このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

C/C++のとんだ落とし穴(ハマっちまったよ)

とある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つの事情が絡んでいます。

  1. ソースコードのエンコーディングがShift_JISであった。
  2. C/C++の単一行コメントでは行継続が可能である。
  3. 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:今僕がいじっているソースコードでは文字列リテラルを使ってないので、文字列リテラルの問題は生じません。