とあるC++コードがコンパイルエラーするんですが、原因がまったく分からなかったんですよ。「そんなバカな?!」という感じ。しばらくハマってしまいましたよ。
結局、C++でもCでも同じことが起きることが分かりました。次は、僕が遭遇したのと同じ現象が起きるC言語のソースコードです。
// -*- coding: sjis -*- // strange.c struct ThreeNums { int x; // 負の数も指定可能 int y; int z; }; int total(struct ThreeNums nums) { return nums.x + nums.y + nums.z; }
コンパイルすると:
$ type tdm-gcc tdm-gcc is aliased to `/c/Installed/TDM-GCC-64/bin/gcc.exe' $ tdm-gcc --version gcc.exe (tdm64-1) 5.1.0 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ tdm-gcc -c strange.c strange.c: In function 'total': strange.c:12:25: error: 'struct ThreeNums' has no member named 'y' return nums.x + nums.y + nums.z; ^
ソースコードを目視している限り、いくら見てもバグは見当たりません。
実は、(しばらく…が続く)
…
…
…
…
…
…
…
…
コメントのせいです。コメントを取ると正常にコンパイルできます。
それにしても「なんでだよー?」と思うでしょ(僕は思いました)。僕の環境・状況が少し特殊だったわけですが、次の3つの事情が絡んでいます。
- ソースコードのエンコーディングがShift_JISであった。
- C/C++の単一行コメントでは行継続が可能である。
- GCCは、Shift_JISで書かれたソースコードを考慮してない。
コメントに書かれている文言の最後の文字「能」ですが、これはShift_JISのヤバイ文字のひとつです。つっても、“最近のわけーもん”は知らないかな? エンコーディングがShift_JISであることを考慮しないと、色々とトラブルを起こす文字です。次のページに危険な文字が一覧されています。
「能」は、バイト列としては 0x94, 0x5cで、2バイト目がバックスラッシュ(または半角円マーク)のグリフを持つ文字と同じです。GCCは、Shif_JISに対して特別な配慮はしないようで、コメントの末尾にバックスラッシュがあると認識します。
そして、なんとですね、C/C++の単一行コメントは、末尾のバックスラッシュによって行継続が出来るんですよ。って、知らねーよ!そんなこと。誰が使うんだよ、そんな機能。トライグラフ知ってたけど、知らんかったわ*1。
この変な仕様により、次のソースコードは問題なくコンパイルできます。
// comment line 1 \ comment line 2 \ commant line 3 int main() {return 0;}
単一行コメントの行継続処理はプリプロセッサで行われるので、コンパイラの「-E オプション」で確認できます。
$ tdm-gcc -E strange.c # 1 "strange.c" # 1 "" # 1 " " # 1 "strange.c" struct ThreeNums { int x; int z; }; int total(struct ThreeNums nums) { return nums.x + nums.y + nums.z; } $ tdm-gcc -E strange2.c # 1 "strange2.c" # 1 " " # 1 " " # 1 "strange2.c" int main() {return 0;}
MicrosoftのVisual C++はShift_JISエンコーディングを考慮しているので(考慮してなかったら怒るよ)、この問題は起きません。また、単一行コメントの行継続には警告をしてくれます。
Windowsだと、次のように書くことはありそうだから、黙っているのはマズイもんね。
char *tmpFileName; // ディレクトリは C:\tmp\ tmpFileName = "001.txt";
GCCだとtmpFileNameは初期化されないままです(恐ろしい)。
今回のケースは不幸な事情が重なった感じですけど、「すごく珍しい」ってほどではない気がします。ご用心ください。
[追記 date="翌日"]
コンパイラがShift_JISのソースコードをそれと認識してないと、コメント以上に文字列リテラルが問題になりますよね*2。GCCにソースコードと実行時のエンコーディングを指定できる、という噂を聞きました。だとすると、次のソースコードは正常にコンパイル・実行できるはずです。
// -*- coding: sjis -*- // a.c #include <stdio.h> int main() { printf("噂では表示可能。\n"); return 0; }
では、噂のおまじない付きでコンパイル・実行。
$ tdm-gcc --input-charset=cp932 --exec-charset=cp932 a.c $ ./a.exe 噂では表示可能。
おおー、出来た。ちなみにオプションを付けないと:
$ tdm-gcc a.c a.c: In function 'main': a.c:6:12: warning: unknown escape sequence: '\202' printf("噂では表示可能。\n"); ^ a.c:6:12: warning: unknown escape sequence: '\216' a.c:6:12: warning: unknown escape sequence: '\201' $ ./a.exe 奄ナは侮ヲ可煤B
0x5c(「\」と解釈される)の直後がエスケープシーケンスとして不正な文字になるんで警告は出ています。結果はご覧のとおりの文字化け。
Shift_JISのソースコードを使わざるを得ないときは、--input-charset, --exec-charsetオプションを付ければいいですね。だけど、このオプション、helpに出ないんだよな: tdm-gcc --help | grep -i charset
→ 何もなし 。
[/追記]
*1:[追記]#defineのときに使えるのは知っていたし、例えば http://d.hatena.ne.jp/m-hiyama/20160227/1456547986 のサンプルで実際に使っています。しかし、コメント内でも有効だとは思わなかった、ってことです。ほんとに「誰得?」[/追記]
*2:今僕がいじっているソースコードでは文字列リテラルを使ってないので、文字列リテラルの問題は生じません。