ざけんな!マイク○ソフト
ここには、 マイクロソフトの OS/開発ツールを使っていて怒りを感じたことを記す。
願わくば、これらを忘れ去る日の来ぬように…。

目次


Visual C++ 5.0 & 6.0 にバグ(1998/12/25、1999/9/1補足)

この前は久々にむかついた。僕は今、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 をデクリメントしないとダメだろ〜。


Gotoh Norifumi <gotoh@is.s.u-tokyo.ac.jp>
Last modified on Tue Feb 22 JST 2000