简体   繁体   English

创建对象实例会触发AV

[英]Creating object instance triggers AV

I have two reference counted classes that hold reference to each other instances. 我有两个引用计数类,它们引用了每个其他实例。 One of those references is marked as [weak] to prevent creating strong reference cycle. 其中一个参考标记为[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

But I cannot successfully finish construction of TBar object instance and exiting Test procedure triggers Access Violation exception at _IntfClear . 但是我无法成功完成TBar对象实例的构建并且退出测试程序会在_IntfClear处触发访问冲突异常。

Exception class $C0000005 with message 'access violation at 0x0040e398: read of address 0x00000009'. 异常类$ C0000005,消息'访问冲突位于0x0040e398:读取地址0x00000009'。

Stepping through debugger shows that TBar.Destroy is called before code reaches writeln(Assigned(Intf)) line and there is no exception during construction process. 单步执行调试器会显示在代码到达writeln(Assigned(Intf))行之前调用TBar.Destroy并且在构造过程中没有异常。

Why is destructor called during construction of an object here and why there is no exception? 为什么在构造对象期间调用析构函数以及为什么没有异常?

Reference counting overview 参考计数概述

To understand what is happening here we need short overview of how Delphi ARC works on reference counted object instances (ones implementing some interface) under classic compiler. 要了解这里发生了什么,我们需要简要概述Delphi ARC如何在经典编译器下对引用计数对象实例(实现某些接口的实例)进行工作。

Reference counting basically counts strong references to an object instance and when last strong reference to an object goes out of scope, reference count will drop to 0 and instance will be destroyed. 引用计数基本上计算对对象实例的强引用,并且当对对象的最后一个强引用超出范围时,引用计数将下降到0并且实例将被销毁。

Strong references here represent interface references (object references and pointers don't trigger reference counting mechanism) and compiler inserts calls to _AddRef and _Release methods at appropriate places for incrementing and decrementing reference count. 此处的强引用表示接口引用(对象引用和指针不触发引用计数机制),并且编译器在适当的位置插入对_AddRef_Release方法的_Release ,以递增和递减引用计数。 For instance, when assigning to interface _AddRef is called, and when that reference goes out of scope _Release . 例如,当调用分配给接口_AddRef时,以及当该引用超出范围_Release

Simplified those methods generally look like: 简化那些方法通常看起来像:

function TInterfacedObject._AddRef: Integer;
begin
  Result := AtomicIncrement(FRefCount);
end;

function TInterfacedObject._Release: Integer;
begin
  Result := AtomicDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

Construction of reference counted object instance looks like: 引用计数对象实例的构造如下所示:

  1. construction - TInterfacedObject.Create -> RefCount = 0 构造 - TInterfacedObject.Create -> RefCount = 0

    • executing NewInstance 执行NewInstance
    • executing chain of constructors 执行构造函数链
    • executing AfterConstruction chain 执行AfterConstruction
  2. assigning to initial strong reference Intf := ... 分配给初始强参考Intf := ...

    • _AddRef -> RefCount = 1

To understand actual problem we need to dig deeper in construction sequence, particularly NewInstance and AfterConstruction methods 要理解实际问题,我们需要深入挖掘构造顺序,特别是NewInstanceAfterConstruction方法

class function TInterfacedObject.NewInstance: TObject;
begin
  Result := inherited NewInstance;
  TInterfacedObject(Result).FRefCount := 1;
end;

procedure TInterfacedObject.AfterConstruction;
begin
  AtomicDecrement(FRefCount);
end;

Why is initial reference count in NewInstance set to 1 and not to 0? 为什么NewInstance初始引用计数设置为1而不是0?

Initial reference count must be set to 1 because code in constructors can be complex and can trigger transient reference counting which could automatically destroy the object during the construction process before it has chance to be assigned to the initial strong reference that will keep it alive. 初始引用计数必须设置为1,因为构造函数中的代码可能很复杂,并且可以触发瞬态引用计数,这可能会在构造过程中自动销毁对象,然后才有机会将其分配给将使其保持活动状态的初始强引用。

That initial reference count is then decreased in AfterConstruction and object instance reference count is properly set for further reference counting. 然后在AfterConstruction减少初始引用计数,并且正确设置对象实例引用计数以进一步引用计数。


Problem 问题

Real problem in this questions code is in fact that it triggers transient reference counting in AfterConstruction method after call to inherited which decreases initial object reference count back to 0. Because of that, object will have its count increased, then decreased to 0 and it will self destruct calling Destroy . 这个问题代码中的实际问题实际上是它调用inherited 之后触发AfterConstruction方法中的瞬态引用计数,这AfterConstruction初始对象引用计数减少回0.因此,对象将其计数增加,然后减少到0并且它将自我毁灭叫Destroy

While object instance is protected from self destruction inside constructor chain, for a brief moment it will be in fragile state inside AfterConstruction method and we need to make sure that there is no code there that can trigger reference counting mechanism during that time . 虽然对象实例在构造函数链中受到保护而不受自我破坏,但在短暂的时刻它将在AfterConstruction方法中处于脆弱状态,我们需要确保那里没有可以触发引用计数机制的代码

Actual code that triggers reference counting in this case is hidden in rather unexpected place and it comes in form of [weak] attribute. 在这种情况下触发引用计数的实际代码隐藏在相当意外的位置,它以[weak]属性的形式出现。 So, the very thing that should prevent instance from participating in reference counting mechanism actually triggers it - this is a flaw in [weak] attribute design reported as RSP-20406 . 因此,应该阻止实例参与引用计数机制的事实上触发它 - 这是[weak]属性设计中报告为RSP-20406的缺陷。


Solution(s) 解决方案(S)

  • If possible, move code that can trigger reference counting from AfterConstruction to constructor 如果可能,将可以触发引用计数的代码从AfterConstruction到构造函数
  • Call inherited at the end of AfterConstruction method instead of the beginning. 调用在AfterConstruction方法结束时inherited ,而不是AfterConstruction开始。
  • Perform some explicit reference counting on your own by calling AtomicIncrement(FRefCount) at the beginning and AtomicDecrement(FRefCount) at the end of AfterConstruction (you cannot use _Release because it will destroy the object) 通过在开始时调用AtomicIncrement(FRefCount)和在AtomicDecrement(FRefCount)结束时AfterConstruction AtomicDecrement(FRefCount)执行一些显式的引用计数(你不能使用_Release因为它会破坏对象)
  • Replace [weak] attribute with [unsafe] (this can only be done if TFoo instance lifetime will never exceed TBar instance lifetime [weak]属性替换为[unsafe] (只有在TFoo实例生命周期永远不会超过TBar实例生命周期时才能执行此操作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM