[英]Creating object instance triggers AV
我有两个引用计数类,它们引用了每个其他实例。 其中一个参考标记为[weak]
以防止创建强大的参考周期。
type
TFoo = class(TInterfacedObject)
private
[weak]
FRef: IInterface;
public
constructor Create(const ARef: IInterface);
end;
TBar = class(TInterfacedObject)
private
FFoo: IInterface;
public
constructor Create; virtual;
destructor Destroy; override;
procedure AfterConstruction; override;
end;
constructor TFoo.Create(const ARef: IInterface);
begin
inherited Create;
FRef := ARef;
end;
constructor TBar.Create;
begin
inherited;
end;
destructor TBar.Destroy;
begin
inherited;
end;
procedure TBar.AfterConstruction;
begin
inherited;
FFoo := TFoo.Create(Self);
end;
procedure Test;
var
Intf: IInterface;
begin
Intf := TBar.Create;
writeln(Assigned(Intf)); // TRUE as expected
end; // AV here
但是我无法成功完成TBar
对象实例的构建并且退出测试程序会在_IntfClear
处触发访问冲突异常。
异常类$ C0000005,消息'访问冲突位于0x0040e398:读取地址0x00000009'。
单步执行调试器会显示在代码到达writeln(Assigned(Intf))
行之前调用TBar.Destroy
并且在构造过程中没有异常。
为什么在构造对象期间调用析构函数以及为什么没有异常?
要了解这里发生了什么,我们需要简要概述Delphi ARC如何在经典编译器下对引用计数对象实例(实现某些接口的实例)进行工作。
引用计数基本上计算对对象实例的强引用,并且当对对象的最后一个强引用超出范围时,引用计数将下降到0并且实例将被销毁。
此处的强引用表示接口引用(对象引用和指针不触发引用计数机制),并且编译器在适当的位置插入对_AddRef
和_Release
方法的_Release
,以递增和递减引用计数。 例如,当调用分配给接口_AddRef
时,以及当该引用超出范围_Release
。
简化那些方法通常看起来像:
function TInterfacedObject._AddRef: Integer;
begin
Result := AtomicIncrement(FRefCount);
end;
function TInterfacedObject._Release: Integer;
begin
Result := AtomicDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
引用计数对象实例的构造如下所示:
构造 - TInterfacedObject.Create -> RefCount = 0
NewInstance
AfterConstruction
链 分配给初始强参考Intf := ...
_AddRef -> RefCount = 1
要理解实际问题,我们需要深入挖掘构造顺序,特别是NewInstance
和AfterConstruction
方法
class function TInterfacedObject.NewInstance: TObject;
begin
Result := inherited NewInstance;
TInterfacedObject(Result).FRefCount := 1;
end;
procedure TInterfacedObject.AfterConstruction;
begin
AtomicDecrement(FRefCount);
end;
NewInstance
初始引用计数设置为1而不是0? 初始引用计数必须设置为1,因为构造函数中的代码可能很复杂,并且可以触发瞬态引用计数,这可能会在构造过程中自动销毁对象,然后才有机会将其分配给将使其保持活动状态的初始强引用。
然后在AfterConstruction
减少初始引用计数,并且正确设置对象实例引用计数以进一步引用计数。
这个问题代码中的实际问题实际上是它在调用inherited
之后触发AfterConstruction
方法中的瞬态引用计数,这AfterConstruction
初始对象引用计数减少回0.因此,对象将其计数增加,然后减少到0并且它将自我毁灭叫Destroy
。
虽然对象实例在构造函数链中受到保护而不受自我破坏,但在短暂的时刻它将在AfterConstruction
方法中处于脆弱状态,我们需要确保那里没有可以触发引用计数机制的代码 。
在这种情况下触发引用计数的实际代码隐藏在相当意外的位置,它以[weak]
属性的形式出现。 因此,应该阻止实例参与引用计数机制的事实上触发它 - 这是[weak]
属性设计中报告为RSP-20406的缺陷。
AfterConstruction
到构造函数 AfterConstruction
方法结束时inherited
,而不是AfterConstruction
开始。 AtomicIncrement(FRefCount)
和在AtomicDecrement(FRefCount)
结束时AfterConstruction
AtomicDecrement(FRefCount)
执行一些显式的引用计数(你不能使用_Release
因为它会破坏对象) [weak]
属性替换为[unsafe]
(只有在TFoo
实例生命周期永远不会超过TBar
实例生命周期时才能执行此操作
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.