The Delphi Bug List

Entry No.
129
コンパイラ - コード生成
グローバル変数がゼロで初期化される動作に依存している場合、オプティマイザは混乱し、誤ったコードを生成する
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 4.03 5.0 5.01 6.0 6.01 6.02 Kylix 1.0
N/A Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists
解説
Reported by Jani Järvinen; checked by Reinier Sterkenburg
サンプル:
program Project1;

{$OPTIMIZATION ON}

Uses Dialogs,SysUtils;

Var I, J: Integer;

begin
   J := 123;
A: I := I+J;
B: I := J-I; { ここで警告: 変数 "I" は初期化されない場合があります }
               ShowMessage(IntToStr(I));
end.

上の簡単なサンプルプログラムは、結果としてゼロを表示するべきです。A の行においてこのプログラムは、グローバル変数がゼロで初期化されるという動作に依存しています。つまり、ここでの加算は必要ではなく、代入(I := J)と同じ意味となります。次に B の行で減算を実行しているので、その結果はゼロであるべきです。しかし最適化が有効になっていると、結果はゴミ値(大抵は-5636096のような値)になります。

グローバル変数がゼロに初期化されるという事がどこに書かれているのか疑問に思うでしょう。それは言語ガイド(Delphi 2 ?)の 47 ページの第四段落に書かれています。:"グローバル変数が明示的に初期化されていない場合、その変数が占めるメモリはゼロで初期化されます。"
これは BP 7.0 以来良く知られた言語の特徴で、Delphi 4 のオンラインヘルプにも記述されています。またこれは、"データセグメントのクリア" としても知られており、全ての初期化されない変数にはゼロがセットされます。例えばポインタには nil 、string には空文字('')がセットされます。

原因:
コンパイラは変数 I をメモリではなく CPU レジスタに置くように最適化し、I のために使われるレジスタはクリアされません。アセンブルされたコードを見れば、EBX レジスタがクリアされず、ゴミ値を含んでいる事が分かるでしょう。このバグの理由は、B の行で変数 I が初期化されないかもしれない為、オプティマイザが混乱して誤ったコードを提供するかもしれないという、誤った警告であるように思えます(この警告は A の行では起こるべきでは?)。スタックやメモリに置かれた変数は、このような状況でもうまく動作しているように見えます。
解決策 / 回避方法
たとえ必要がなくとも、グローバル変数を初期化するようにします。:
...
begin
  I := 0; { 必要ではなくても初期化します }
  J := 123;
  ...
(代案: A の行を、I を使わない形に変更する "I := J") さらにコードの一部を再整理するか書き直すことは助けになるように思えます。しかしこの種のバグが完全にドキュメント化されるまで、本当に確実な唯一の方法は最適化をオフにすることだけです。しかしながらこのバグは特殊なケースであり、"普通の" Delphi プログラムでは問題ないでしょう。
ユーザーからのコメント
Hallvard Vassbotn
30 Aug 2001  08:04 AM GMT
Chief Delphi Architect の Chuch Jazdzewski によれば、この振る舞いは設計通りであり、従ってバグではなく gotcha として扱われるべきだとのことです。

この場合、I と J は begin..end ブロックに対するローカル変数であると見なされ、コンパイラによってその "procedure" のローカル変数として宣言されているかのように割り当てられます。この変数宣言と begin..end の間に本当の procedure 宣言があれば、これらはグローバル変数として扱わるでしょう。
Jordan Russell
09 Sep 2001  04:39 PM GMT
問題はコンパイラが最適化を用いることではありません(それはバグではありません)。最適化を有効にしたときにプログラムが異なる振る舞いをすること、それがバグです。

Delphi のドキュメントで "$O" を調べると次の記述があります。
Delphi の Object Pascal コンパイラにより実行されるすべての最適化は、プログラムの意味を変更しないことが保証されています。言い換えると、Delphi ではプログラマの特別な注意を必要とするような「安全でない」最適化は行われないという事です。

最適化の一環としてコンパイラが特定のグローバル変数をレジスタに格納しようとするのはすばらしい事ですが、一貫性のためにはプログラムが初期化を行わない場合でも、コンパイラがそれらのレジスタを初期化しなければならないと思います。
Pete
01 Dec 2002  12:04 AM GMT
私はこのバグは少し弱いと思います。間違いなく全てのまじめなプログラマは、計算を行う前に変数を初期化するでしょう。
私(ASP コーダー)のような人間にとっては重要な問題ではありません。
Latest update of this entry: 2002-04-03
本家 The Delphi Bug List のエントリーはこちら
The Delphi Bug List 日本語訳 へ