なぜバグがなくならないのか。そこにはいろんな原因があるんだけど…という話をしよう。今回は一言で言えば、計算機がバカだから、という理由。
世の中に、浮動小数点数を信用していない人は多くいると思うけど、実は整数演算も信用できるものではない。
int main(int argc, char **argv)
{
unsigned int a=10,b=20;
unsigned int c;
c=a-b;
printf(“d: %d-%d=%d\n”, a,b,c);
printf(“u: %u-%u=%u\n”, a,b,c);
if(c > 3){
printf(“yes1! %u > 3\n”, c);
}
if(a-b > 3){
printf(“yes2! %u-%u > 3\n”, a, b);
}
return 0;
}
gcc -Wallでワーニングもなく粛々とコンパイルされたその麗しき実行ファイルは、以下のような出力をしてくれる。別に不思議なことは何も…ない!?
u: 10-20=4294967286
yes1! 4294967286 > 3
yes2! 10-20 > 3
10-20 > 3なのだ。奴等はそう信じているのだ。整数はダメ! 疑え! ダウトだ! これだから計算機というものは信用できない(笑)。
実際にあった失敗例としては、タイムアウトを判定するときに時刻情報をunsignedな整数で定義していて、時刻情報同士で引き算をして比較する。でもって何かの拍子に時刻が狂うと(例えばntpはこういうことをけっこうする)、いきなりタイムアウトが発生してしまう。ntpが時刻を戻すときって、逆転しないように徐々に進みを遅くしていくって話を昔聞いたんだけどなぁ。そういうのは特別な設定だけなのかもしれない。
というわけで、「time_tを64bitにする前にとりあえずunsignedにして延命しよう」なんてバカな考えには賛同できない。人類の英知を持ってすれば、2038年までなら64bitにできるはずだ(地球では2038年1月19日にtime()==0x7fffffffを迎える)。
まあ、要はunsignedなんて使うなアホってな話になるだけなんだけど(笑)。なんでも「負の数はありえない=unsigned」だと思っていると、まんまと騙される。ファイルサイズとか。配列の長さとか。とにかくunsignedは使うな。っていうかむしろコンパイラがはやくunsignedを禁止するのがよろしかろう。若者よ、あんなのしょせんはゴマカシなのだ。はやく目を覚ませ(笑)。
賢い人は、size_tがunsignedなのを見て、バカばっかだな、と思うことだろう。size_tを引き算したらダメだ。これ基本。sizeof(x)も引いたらダメ。strlen()の戻り値も引いたらダメ。htonl() やhtons()も引いたらダメだ。幼稚な計算機に引き算などという高等なことはできないのだ。そこが人間様との大きな違いだ(笑)。
はやくC言語に標準で無限桁の整数演算を定義してほしい。そして国会議員は一刻も早くunsignedを法律で禁止してほしい。その法律こそがバグを半分に減らす魔法の法律として永遠に語り継がれるであろう。
…なんだそりゃ。
(追記) 2004-08-20 15:58
gccはsizeof()をunsignedにしますが、例えばtcc(Tiny C Compiler)(free.fr)はsizeof()をsigned intとして扱ってくれるので、sizeof() に関してはtccと心中する気のある人は引き算しても大丈夫です。どっちが仕様に従っていないのか、あるいはどっちでもいいのかは不明です。