C言語 間違い探し 答え編
こちらの記事の答えである。
そもそも、いったい何を間違ったのか
再掲すると、例外を引き起こしたのはこのコードである。
#include <stdio.h>
int main()
{
const static char* name[] =
{
"aaa"
"bbb"
"ccc"
};
printf("%s\n", name[0]);
printf("%s\n", name[1]);
printf("%s\n", name[2]);
return 0;
}
私が間違ったのは「,」忘れである。
ここ、この「,」たち。
これを付け忘れている。
しかも、この「,」忘れがコンパイルエラーにならないというのである。
Visual Studioのバグでね?
初めはそれさえ疑った。
だってね、たいていの「,」は忘れるとコンパイルエラーになりますやん。
これだって
func(a b c);
これだって
enum {
AAA
BBB
CCC
};
いやいや、これにしたって
const static int number[] =
{
1
2
3
};
全部、コンパイルエラーになる。
なんでこれだけがコンパイルエラーになりまへんのん?
const static char* name[] =
{
"aaa"
"bbb"
"ccc"
};
なので調べてみた
カーニハンC
索引の次の項目を読んでみたが、関連する記載はなかった。
文字定数
文字列
文字列定数
カーニハンC 第2版(ANSI規格準拠)
ああ。ありました。
なるほど。
この仕様か。
だから、このコード
const static char* name[] =
{
"aaa"
"bbb"
"ccc"
};
これは次のようにコンパイルされたわけだ。
const static char* name[] =
{
"aaabbbccc"
};
この場合、
name[0] = "aaabbbccc"
となるが
name[1]
name[2]
は、ない。
ないというのは、ないんである。
0に設定されているのではなく、存在そのものがない。
const static char* name[3];
ではなくて
const static char* name[1];
ということだ。
配列1つ分しか領域は確保されていないので、
name[1]
name[2]
はオーバーランということになる。
領域侵犯である。
clang は(null)を表示して
Visual Studio は例外になった。
不正ポインタの挙動はマチマチである。
言語仕様としても規定されていない。
Visual Studioさん、すみません。
言いがかりでした。
これは言語仕様でした。
JIS (延長戦)
ついでなのでJIS も確認してみた。
実はこちらを先に確認していたんだが、こちらはこちらで軽く酩酊状態に陥る。
早速、クラクラしてきた。正確に書こうとしてまどろっこしい文章になってるんだが、簡単に書くとこうなる。
単純文字列リテラル字句、とか
ワイド文字列リテラル字句、とか
多バイト文字、とか
そんな風に称するから、それは正確さを求めてのことなんだろうけど、肝心の焦点がぼやけそうである。
さらに、次のような例もある。
どうでもいいが、「\」を「\」と、全角で書くのは何故だろう。
フォントによっては読みにくいから?
確かに
\x
と書くと縦線のように見えなくもないのであって、電子データであればコピーすればすむものの印刷するとなると目で見てタイピングせねばならず、となると読み間違いにくい表記をめざす気持ちはわからないではないんだが、それにしてもなんだかなぁと思わずにはいられない。
ついでに「逆斜線表記」というのもなんだかなぁ、である。「エスケープシーケンス」でいいんじゃね?
そんなに必死になって訳さなくても。
この際だからついでに言うと
「逆斜線表記が実行文字集合の一つの要素に変換されるので」
とあるが、あとに続く文章では変換されていないので何を言っているのかわかりにくい。
私流に書き直すとこうなる。
なるほど、そういうことであったか。
文字列 "\x12" というのは
16進数で「12」という変換にする
というエスケープシーケンスである。「12」の数値は「00~FF」までを自由に選択できて、16進数文字列を連続することができる。この「連続できる」というのが肝である。結果
"\x123"
と書いても
0x12、0x33
という数値には展開されない。
なので次のようになる。
"\x01" → 0x01
"\x012" → 0x12
"\x01" "2" → 0x01 0x32
なんで "\x123" ではないのかというと、"\x123" は clang ではエラーになったからである。JIS には
とあるので、これまた微妙ではあるがこういうことはC言語ではよくある。JIS(ISO)に対して100点満点のコンパイラというのはなかなか難しかろうと思われる。
ついでにもう1つ。
翻訳フェーズ(6)ってなんだというと、次に書いてある。
ちなみに他のフェーズも列挙するとこうなる。
(1) 文字コード解釈。3文字表記変換。
(2)「\改行」カット
(3)コメントカット
(4)プリプロセッサ展開
(5)エスケープシーケンス展開
(6)隣接文字列連結
(7)コンパイル
(8)リンク
隣接文字列の連結というのは「(7)コンパイル」に含まれるのではなくって独立しているんである。コメントカットやプリプロセッサ展開、エスケープシーケンス展開と同列だ。この連結処理を終えてからコンパイルするわけで、コンパイルエラーになりようがない。
余談ですが
カーニハンCを読んでいると、他にも「ん? 」と思うことが………。
え?
そうなん?
これまた初見である。
なのでやってみた。
#include <stdio.h>
int main() {
printf("こういう長い文章を printf で出力しようとすると\
ひたすら長い1行になってコードが見にくくって\
しかたがない。\
とはいうものの、いちいち printf を分けるのも\
面倒じゃない。ちょっと改行して\
コードを見やすくしたいんです。\n");
return 0;
}
実行結果
こういう長い文章を printf で出力しようとするとひたすら長い1行にな ってコードが見にくくってしかたがない。とはいうものの、いちいち printf を分けるのも面倒じゃない。ちょっと改行してコードを見やすくしたいんです。
ちなみにこの「\改行」をカットする処理は「翻訳フェーズ(2)」にあたる。
ふぅ~。
この記事が気に入ったらサポートをしてみませんか?