gpt4 book ai didi

delphi - 创建对象实例触发 AV

转载 作者:行者123 更新时间:2023-12-03 14:47:59 27 4
gpt4 key购买 nike

我有两个引用计数类,它们相互引用实例。其中一个引用被标记为 [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 处触发访问冲突异常.

Exception class $C0000005 with message 'access violation at 0x0040e398: read of address 0x00000009'.



单步调试器显示 TBar.Destroy在代码到达 writeln(Assigned(Intf)) 之前调用线,施工过程中也不异常(exception)。

为什么这里在构造对象的时候会调用析构函数,为什么没有异常?

最佳答案

引用计数概述

要了解这里发生了什么,我们需要简要概述 Delphi ARC 如何在经典编译器下处理引用计数对象实例(实现某些接口(interface)的实例)。

引用计数基本上计算对对象实例的强引用,当对对象的最后一个强引用超出范围时,引用计数将降至 0,并且实例将被销毁。

此处的强引用表示接口(interface)引用(对象引用和指针不会触发引用计数机制),编译器会插入对 _AddRef 的调用和 _Release在适当的地方增加和减少引用计数的方法。例如,当分配给接口(interface) _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;

引用计数对象实例的构造如下所示:
  • build - TInterfacedObject.Create -> RefCount = 0
  • 执行NewInstance
  • 执行构造函数链
  • 执行AfterConstruction链条
  • 分配给初始强引用 Intf := ...
  • _AddRef -> RefCount = 1

  • 要了解实际问题,我们需要更深入地挖掘构造顺序,特别是 NewInstanceAfterConstruction方法
    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,因为构造函数中的代码可能很复杂,并且可能会触发 transient 引用计数,这可能会在构造过程中自动销毁对象,然后才有机会将其分配给使其保持事件状态的初始强引用。

    然后在 AfterConstruction 中减少初始引用计数。并且正确设置了对象实例引用计数以进行进一步的引用计数。

    问题

    这个问题代码中的真正问题实际上是它触发了 AfterConstruction 中的 transient 引用计数。方法 之后 调用 inherited它将初始对象引用计数减少回 0。因此,对象的计数将增加,然后减少到 0,并且它将自毁调用 Destroy .

    虽然对象实例在构造函数链中受到保护,不会自毁,但它在 AfterConstruction 内部会暂时处于脆弱状态。方法,我们需要确保在此期间没有可以触发引用计数机制的代码 .

    在这种情况下触发引用计数的实际代码隐藏在相当意想不到的地方,它以 [weak] 的形式出现。属性。因此,应该阻止实例参与引用计数机制的事情实际上会触发它——这是 [weak] 中的一个缺陷。属性设计报告为 RSP-20406 .

    解决方案
  • 如果可能,从 AfterConstruction 移动可以触发引用计数的代码给构造函数
  • 调用 inheritedAfterConstruction 结尾方法而不是开头。
  • 通过调用 AtomicIncrement(FRefCount) 自行执行一些显式引用计数在开头和 AtomicDecrement(FRefCount)AfterConstruction 结尾(不能使用 _Release 因为它会破坏对象)
  • 替换 [weak] [unsafe] 的属性(只有在 TFoo 实例生命周期永远不会超过 TBar 实例生命周期
  • 时才能这样做

    关于delphi - 创建对象实例触发 AV,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57364982/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com