ざけんな!マイク○ソフト |
---|
この前は久々にむかついた。僕は今、Windows上での開発を手掛けている。 そして必要にせまられ、Visual C++ 5.0 を使っていたのだが、Debug版 (最適化なし、デバッグオプション付き)ではちゃんと動くプログラムが、 Release版(最適化あり、デバッグオプションなし)にしたとたんに、 バグり出すのだ。「なんじゃ、こりゃ〜」と思っていろいろ調べてみたら、 どんな部分でバグるのかが判った。
まず、下のようなプログラムを Win32 Console Application プロジェクト とでもして入力する。
#include <stdio.h> #define XDIM 8 #define YDIM 16 int test(int xmax, int ymax) { int ar[XDIM][YDIM]; int x, y; for (x = 0; x < xmax; x ++) for (y = 0; y < ymax; y ++) ar[x][y] = 0; /* bug */ for (x = xmax-1, y = ymax-1; x >= 0; x --, y --) ar[x][y] = 1; for (y = 0; y < 8; y ++) { printf("y=%d :", y); for (x = 0; x < 8; x ++) printf(" %d", ar[x][y]); printf("\n"); } return 0; } int main() { return test(8, 8); }で、これを Debug 版としてビルドして実行してみると、以下のように まともな結果が表示される。
y=0 : 1 0 0 0 0 0 0 0 y=1 : 0 1 0 0 0 0 0 0 y=2 : 0 0 1 0 0 0 0 0 y=3 : 0 0 0 1 0 0 0 0 y=4 : 0 0 0 0 1 0 0 0 y=5 : 0 0 0 0 0 1 0 0 y=6 : 0 0 0 0 0 0 1 0 y=7 : 0 0 0 0 0 0 0 1今度は Release版でビルドして実行してみる。すると…
y=0 : 0 0 0 0 0 0 0 0 y=1 : 0 0 0 0 0 0 0 0 y=2 : 0 0 0 0 0 0 0 0 y=3 : 0 0 0 0 0 0 0 0 y=4 : 0 0 0 0 0 0 0 0 y=5 : 0 0 0 0 0 0 0 0 y=6 : 0 0 0 0 0 0 0 0 y=7 : 1 1 1 1 1 1 1 1…。なんじゃこりゃ〜、となるわけ。マクロの YDIM の値を 16より大きい2の累乗の数にするとバグる。disassemble したコードを 眺めてみると、確かにリスト中の /* bug */ 以下の for 文が 間違ってコンパイルされている。 ループの誘導変数が2つ以上あるときに発生するようだ。
このバグの原因を想像するに、 間接アドレッシング指定まわりの最適化がらみだろう。 というのも、i386系 CPU の間接アドレッシングでは、 あるレジスタにあるレジスタの中身を 1/2/4/8 倍して足しさらに定数を足した値 を使って参照、ということができる。だから、YDIM の値が 1/2/4/8 であれば、 この間接アドレッシング法を利用した最適化が適用できる。 ところが 16 になるとそうはいかないので、 ちゃんと 16倍して足すとかいう処理が必要だ。 どうせ、ここらへんでちゃんとチェックを入れていないのが原因なんだろう。
以上のバグ、最近 Visual C++ 6.0 に触れる機会があり、 早速試してみたが再現した。とっとと直せ〜。 それにしても、どうやって回避すれば良いんだろう…。
1999/8/31 補足
よーく考えると、sizeof(int)=4 だから、上の推論は間違っている気がしてきた。
まぁいいや。
1999/9/1 補足
sizeof(int)=4でも、(x+y*16)*sizeof(int) で計算すれば、
やっぱり上の推論が成り立つことを発見。ありゃりゃ。
それにしても、Visual C++ 6.0 sp3 で試してもまだ直っていない。
ちゃんと報告は行っているはずなのに…。
まったく、いつになったら直るんだろう?
2000/2/21 補足
要望があったので、間違ったコードのアセンブラリストを載せる。
コンパイラは Microsoft の誇る(?) Visual C++ Ver 6.0 sp3。
該当する for ループのコードだけを抜き出すと…
1: lea edx,[esi-1] ;; 2: test edx,edx ;; if (xmax - 1 < 0) 3: jl loop_end ;; goto loop_end; 4: mov ecx,edx ;; 5: mov eax,edx ;; 6: sub ecx,esi ;; 7: mov esi,1 ;; 8: add ecx,ebx ;; ecx = ymax - 1; 9: shl eax,4 ;; eax = (xmax - 1) * 16; 10: inc edx ;; edx = xmax 11: loop_start: 12: lea edi,[eax+ecx] ;; edi = eax + ecx; 13: sub eax,10h ;; eax -= 16; 14: dec edx ;; edx --; 15: mov dword ptr [esp+edi*4+10h],esi ;; as[edi] = 1; 16: jne loop_start 17: loop_end:10行目の edx をインクリメントしている部分と、 14行目の edx をデクリメントしている部分が間違っている。 特に14行目。ここは ecx をデクリメントしないとダメだろ〜。