6

次のコードで予期しないアクセス違反エラーが発生します。

program Project65;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;

type
  ITest = interface
  end;

  TTest = class(TInterfacedObject, ITest)
  end;

var
  p: ^ITest;

begin
  GetMem(p, SizeOf(ITest)); 
  p^ := TTest.Create; // AV here
  try
  finally
    p^ := nil;
    FreeMem(p);
  end;
end.

インターフェースは別の方法で使用する必要があることを知っています。ただし、このアプローチを使用するレガシー コードベースに取り組んでいます。ITest を配置するために SizeOf(ITest) メモリを確保するだけでは不十分であることに非常に驚きました。

興味深いことに、最初の行を次のように変更すると

GetMem(p, 21);

AVがなくなったより。(20 バイト以下は失敗)。これに対する説明は何ですか?

(Delphi XE2 Update 4 + HotFix を使用しています)

コードがいかにひどいものであるかについてコメントしたり、これを適切にコーディングする方法を提案したりしないでください。代わりに、SizeOf(ITest) = 4 ではなく 21 バイトを予約する必要がある理由を教えてください。

4

1 に答える 1

25

あなたが効果的に書いたのは、舞台裏で次のロジックを実行していることです。

var
  p: ^ITest;
begin
  GetMem(p, SizeOf(ITest));
  if p^ <> nil then p^._Release; // <-- AV here
  PInteger(p)^ := ITest(TTest.Create);
  p^._AddRef;
  ...
  if p^ <> nil then p^._Release;
  PInteger(p)^ := 0;
  FreeMem(p);
end;

GetMem()割り当てたものをゼロにする保証はありません。新しいオブジェクト インスタンスをインターフェイス varaiable に割り当てるときに、バイトがゼロでない場合、RTL は既存のインターフェイス参照が既に存在すると見なし、その_Release()メソッドを呼び出そうとします。実際のオブジェクトによってサポートされていないため、AV が発生します。実例。事前に割り当てられたバイトをゼロにする必要があります。そうすると、RTL はインターフェイス参照を認識し、そのメソッドnilを呼び出そうとしなくなります。_Release()

program Project65;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;

type
  ITest = interface
  end;

  TTest = class(TInterfacedObject, ITest)
  end;

var              
  p: ^ITest;              

begin              
  GetMem(p, SizeOf(ITest));               
  try
    FillChar(p^, SizeOf(ITest), #0); // <-- add this!
    p^ := TTest.Create; // <-- no more AV
    try
      ...
    finally
      p^ := nil;
    end;
  finally
    FreeMem(p);
  end;
end.
于 2012-06-12T00:40:31.320 に答える