Reported by Antoine Leca; checked by Duncan Murdoch, Reinier Sterkenburg
型付定数として生成された古いスタイルのオブジェクトを指す継承ポインタを通して仮想メソッドを呼び出したとき、Delphi 2/3/4 は正しいメソッドを選択しません。それは二つだけ足りない、つまり、正しいメソッドよりも二つ前のメソッドを選択してしまいます。実際のところこれは、まるで "object" が "class" と同じ VMT を持っている(これは誤りです)かのようです。
object はコンストラクタを持つ必要がありません(BP 7 のマニュアルを参照)。仮想メソッドを持つオブジェクトが初期化されるにはコンストラクタが呼ばれなければならないことは、この言語の規則です。しかしながらBP 7のコンパイラはそれ(仮想メソッドを持つobjectを、コンストラクタを呼ばずに使用する事)を許可しており、なおかつコンストラクタの呼び出しを行わない定数オブジェクトの宣言も許しています(これは文書化された振る舞いです)。しかしDelphi の 32 ビットコンパイラはそれほど寛容ではありません。
これはコンパイラのバグです。なぜなら、ドキュメント化はされていませんが、古いスタイルのオブジェクトを扱うための機能は全て提供されているからです。残念ながらコンパイラは、型付定数オブジェクトの VMT フィールドを間違った値で初期化してしまうのです。
以下のソースファイルはこの問題を確認するためのものです。これは BP7 と Delphi 1.02 ではうまく動作します。その出力は次のようになります:
TObj.c elem=1
TObj.c elem=1
しかし、Delphi 2.01では失敗し、出力は次のようになります:
TObj.c elem=1
TObj2.a elem=1
Delphi 3.01,4.02,5.01,6.02では、2行目の実行時に Access Violation が生成されます。
Kylix(1.0)では、"Program Test416 received signal SIGSEV(11).Process stopped. Use Step or Run to continue." が発生します。
ソース:
{$ifdef WINDOWS}
uses WinCRT;
{$endif}
type TObj=object
elem:Byte;
procedure a;virtual;
procedure b;virtual;
procedure c;virtual;
end;
type
PObj=^TObj;
type
TObj2=object(TObj)
procedure a; virtual;
end;
procedure TObj.a;
begin
WriteLn('TObj.a elem=',elem);
end;
procedure TObj.b;
begin
WriteLn('TObj.b elem=',elem);
end;
procedure TObj.c;
begin
WriteLn('TObj.c elem=',elem);
end;
procedure TObj2.a;
begin
WriteLn('TObj2.a elem=',elem);
end;
const
Obj:TObj2 = (elem:1);
const
Ptr:PObj = @Obj;
begin
Obj.c;
Ptr^.c;
end.
|