簡體   English   中英

創建對象實例會觸發AV

[英]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;

引用計數對象實例的構造如下所示:

  1. 構造 - TInterfacedObject.Create -> RefCount = 0

    • 執行NewInstance
    • 執行構造函數鏈
    • 執行AfterConstruction
  2. 分配給初始強參考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,因為構造函數中的代碼可能很復雜,並且可以觸發瞬態引用計數,這可能會在構造過程中自動銷毀對象,然后才有機會將其分配給將使其保持活動狀態的初始強引用。

然后在AfterConstruction減少初始引用計數,並且正確設置對象實例引用計數以進一步引用計數。


問題

這個問題代碼中的實際問題實際上是它調用inherited 之后觸發AfterConstruction方法中的瞬態引用計數,這AfterConstruction初始對象引用計數減少回0.因此,對象將其計數增加,然后減少到0並且它將自我毀滅叫Destroy

雖然對象實例在構造函數鏈中受到保護而不受自我破壞,但在短暫的時刻它將在AfterConstruction方法中處於脆弱狀態,我們需要確保那里沒有可以觸發引用計數機制的代碼

在這種情況下觸發引用計數的實際代碼隱藏在相當意外的位置,它以[weak]屬性的形式出現。 因此,應該阻止實例參與引用計數機制的事實上觸發它 - 這是[weak]屬性設計中報告為RSP-20406的缺陷。


解決方案(S)

  • 如果可能,將可以觸發引用計數的代碼從AfterConstruction到構造函數
  • 調用在AfterConstruction方法結束時inherited ,而不是AfterConstruction開始。
  • 通過在開始時調用AtomicIncrement(FRefCount)和在AtomicDecrement(FRefCount)結束時AfterConstruction AtomicDecrement(FRefCount)執行一些顯式的引用計數(你不能使用_Release因為它會破壞對象)
  • [weak]屬性替換為[unsafe] (只有在TFoo實例生命周期永遠不會超過TBar實例生命周期時才能執行此操作

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM