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