C言語デバッグ小噺

先日C/C++のことを書いたせいか、ふと昔聞いたC言語プログラムのデバッグ小噺を思い出した。これは僕の体験談じゃなくてひとから聞いた話ね。もう記憶があやふやなので細かいところは間違っているかもしれない。

これはUNIX系OSで動いていたとある巨大なプログラムを、ウィンドウズに移植しようとして発生したバグの話。そのバグは、とある構造体群の周辺で発生した。そのシステムでは、多くの構造体が次のように定義されていた。

struct Data
{
  char  typechar[2];
  short typenum;
  ...
};

この typechar とか typenum というのは構造体の型を識別するためのコードだ。よーするに、あらゆるデータの先頭にそのデータ型を識別するためのコードが埋め込まれていたわけ。例えば AB-0001 みたいな、アルファベット2文字と16ビットの数字でデータタイプを識別してたんですな。Cには実行時型情報とかメタ情報とかないのでやむなく、こういうコードを埋め込んでそのオブジェクトの型を識別してたわけだ。いかにもC言語らしい設計だよね。

さて、一体どんなバグが発生したのか。ウィンドウズ上ではこの型情報識別コードに関する処理が正常に動作しなくなったのだ。原因は何だと思います?


一つ目のヒント: これだけで分かったら鋭い!

移植元(UNIX)はビッグエンディアン、移植先(ウィンドウズ)はリトルエンディアンだった。


二つ目のヒント: 勘のいい人はこれで分かるかも。

typenum は 16bit だが、256 以上の数値が入ることは(滅多に)なかった。


三つ目のヒント: これで分かるよね?

typechar の処理に strcmp() とか使ってた。


答えを書きます。
まず、随所にこんなコードが書いてあったわけ。

if ( strcmp( dat.typechar, "AB" ) == 0 ) {
  ...
}

いやもう、こりゃ大変お粗末なバグですよ。strcmp() の引数の文字列は終端がヌル文字でなくてはならないのに、typechar は null-terminated じゃないんだから。だけど、UNIXではたまたま動いちゃってたんだよね。なぜなら、typechar に続く typenum が256未満で、かつビッグエンディアンだったから。
ビッグエンディアンでは1バイト目が上位桁、2バイト目が下位桁だ。typenum は256未満だから、1バイト目は必ず 0 だったことになる。これがたまたま typechar のヌル文字の役割を果たしていたため、UNIX環境では期待通りに動作してしまっていたのだ。
しかしリトルエンディアンの環境に移ると1バイト目が下位桁になる。つまり1バイト目が 0 ではなくなってしまうので、ヌル文字の役割を果たさなくなってしまうわけだ。そうすると上記の if 文が失敗するようになってしまう。


この苦労話、語ってくれた人は「いやー苦労したよ〜」なんていいながら妙に嬉しそうな顔をしてた。こういう「デバッグ苦労自慢」する人っているよね。っていうか、僕もするけどさ(笑。

時は移ろい、C# しか使わないようになって、すっかりこの手のデバッグ苦労話からは縁遠くなった。いいことだ。