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.

我知道接口应该以不同的方式使用。但是,我正在研究使用这种方法的遗留代码库。而且我很惊讶地看到保留 SizeOf(ITest) 内存来放置 ITest 是不够的。

现在有趣的是,如果我将第一行更改为

GetMem(p, 21);

比AV消失了。(20 字节或更少失败)。对此有何解释?

(我正在使用 Delphi XE2 Update 4 + HotFix)

请不要评论代码有多可怕或建议如何正确编码。请回答为什么需要保留 21 个字节而不是 SizeOf(ITest) = 4?

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()不能保证将其分配的内容归零。当您将新对象实例分配给接口变量时,如果字节不为零,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 回答