The Delphi Bug List

Entry No.
452
■分類
小さいオブジェクトが奇妙なランタイムエラーを生成する場合がある
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 Bjrn Sahlen; checked by Reinier Sterkenburg

再現させるには、以下のコードをコンパイルし、実行します。

これはバグです、なぜなら:
もしコードに(私はないと思うが)何らかの間違いがあるなら、コンパイラがエラーを報告するべきです。しかし、実行時に次のメッセージが表示されます。
プロジェクト ... が EAccessViolation クラスの例外を生成しました。'モジュール '...' の アドレス ...'

Kylix では、振る舞いが少し違います: TForm1.FormCreate の次の行

  AObj.Prop:= 3;
ここで、SIGSEGV エラーが発生し、続いて Access Violation が発生します。

より詳しく調査(ステップ実行/トレース実行)してみると、プログラムは自発的に PROCEDURE IObj.Proc に飛び、そこでエラーが発生していることが明らかになります。
注意:これは最適化をオフにした状態で発生します。
どういうわけか、フィールド(プロパティ)のアドレスに関して混乱しているように見えます。

Delphi 1 では object 内のプロパティを許可していないため、このバグは存在しません。

コード

unit DelphiErr2U;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

TYPE
  IObj= Object
//  FieldThatMakesTheTotalFieldSizeGreaterThanFourBytes: Byte;
    I: LongInt;
    FUNCTION  Func: LongInt;
    PROCEDURE Proc (X: LongInt);
    PROPERTY  Prop: LongInt  Read Func  Write Proc;
    END;

FUNCTION IObj.Func: LongInt;
BEGIN
  Result:= 5;
END;

PROCEDURE IObj.Proc(X: LongInt);
BEGIN
  I:= X + 1;
END;

procedure TForm1.Button1Click(Sender: TObject);
VAR  AObj: IObj;  Z: LongInt;
begin
  AObj.Prop:= 3;
  Z:= AObj.Prop;
  Caption:= IntToStr(Z);
end;                     //  この procedure を抜ける時に Access violation が発生する。

end.
解決策 / 回避方法
私が容認出来る唯一の回避方法は、object の代わりに record と標準的な function と procedure とを使用する事です。もうひとつの回避方法は、object のサイズが少なくとも5バイトになるようにする事です。
ユーザーからのコメント
Chris R. Timmons
30 Jun 2001  04:56 AM GMT
例として書かれているコードには二つのバグがあります。

ひとつ目は、IObj が"古いスタイル"のオブジェクトとして宣言されている事です。ボーランドはキーワード object を用いて宣言されたクラスに関連する幾つかのバグについて認識しています。IObj の宣言は "IObj = object" ではなく、"IObj = class" であるべきです。

二つ目のバグは Button1Click 内にあります。AObj がインスタンス化されていません!当然これは Access violation を起こします。このコードは以下のようであるべきです。

AObj := IObj.Create;
try
AObj.Prop := 3;
Z := AObj.Prop;
Caption := IntToStr(Z);
finally
AObj.Free;
end;

これら二つの変更により、私の環境(Delphi 5.01, Win2k SP2)では正常に動作するようになります。
Joanna Carter
20 Apr 2002  12:54 PM GMT
AObj はポインタではなく変数です。このオブジェクトにアクセスするには、その変数のアドレスを取得しなければいけません。以下のコードは D6.02 では正常に動作しているように見えます。

procedure TForm1.Button1Click(Sender: TObject);
var
  AObj: IObj;
  Z: LongInt;
begin
  Iobj(@AObj).Prop:= 3;
  Z := Iobj(@AObj).Prop;
  Caption:= IntToStr(Z);
end; 
AE
15 Feb 2003  11:58 AM GMT
これは全て、ひとつの問題について説明していません: なぜアクセス違反はアクセスしたところでは発生せず、プロシージャを抜けるところまで遅延するのか?

私の場合、これはとても良く起こります: ある種の副作用(たいていは動的に割り当てられる文字列が関与する)は、コード中の奇妙な場所で例外を生成したり、IDE内のアクセス違反を生成したり、最悪の場合には次にF7を押した時にIDEが何も言わずに終了したりします。これでは問題の根本がどこなのかのアイデアも得られないし、トレースする事も出来ません。これは特別に扱い難く、Delphi 6 PE でもまだ当てはまります。
Latest update of this entry: 2002-04-03
本家 The Delphi Bug List のエントリーはこちら
The Delphi Bug List 日本語訳 へ