2012-06-11 21 views
6

我得到了意想不到的訪問衝突錯誤在下面的代碼時:奇怪的AV存儲的德爾福接口引用

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var 
    p: ^ITest; 

begin 
    GetMem(p, SizeOf(ITest)); 
    p^ := TTest.Create; // AV here 
    try 
    finally 
    p^ := nil; 
    FreeMem(p); 
    end; 
end. 

我知道的接口,應區別使用。不過,我正在使用這種方法的遺留代碼庫。我很驚訝地發現,預留SizeOf(ITest)內存不足以將ITest放在那裏。

現在,有趣的是,如果我改變的第一行

GetMem(p, 21); 

比AV不見了。 (20字節或更少失敗)。這是什麼解釋?

(我使用德爾福XE2更新4 +修復)

請不要在代碼是多麼可怕的意見或建議如何這可能是正確編碼。相反,請回答爲什麼需要保留21個字節而不是SizeOf(ITest)= 4?

+0

AV =訪問衝突? – kol

+0

你的代碼看起來很奇怪。爲什麼你需要「^ ITest」和GetMem/FreeMem對? TTest是TInterfacedObject的後代,所以p應該只是一個ITest。它是引用計數的,所以它會在超出範圍時自動銷燬。不需要使用GetMem/FreeMem。 – kol

+1

這是使用接口的完全不正確的方式。你能解釋一下你希望完成的是什麼嗎?也許有人能指出你朝着更好的方向發展? –

回答

26

你已經有效地寫入正在做幕後以下邏輯是什麼:

var 
    p: ^ITest; 
begin 
    GetMem(p, SizeOf(ITest)); 
    if p^ <> nil then p^._Release; // <-- AV here 
    PInteger(p)^ := ITest(TTest.Create); 
    p^._AddRef; 
    ... 
    if p^ <> nil then p^._Release; 
    PInteger(p)^ := 0; 
    FreeMem(p); 
end; 

GetMem()不能保證零出它的分配。當您將新的對象實例分配給可變接口時,如果字節不是零,則RTL會認爲已經存在接口引用,並嘗試調用其方法,導致AV,因爲它沒有由實際支持對象實例。你需要事先零出分配字節,則RTL會看到一個nil接口的參考,而不是試圖調用其_Release()方法了:

program Project65; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    SysUtils; 

type 
    ITest = interface 
    end; 

    TTest = class(TInterfacedObject, ITest) 
    end; 

var    
    p: ^ITest;    

begin    
    GetMem(p, SizeOf(ITest));    
    try 
    FillChar(p^, SizeOf(ITest), #0); // <-- add this! 
    p^ := TTest.Create; // <-- no more AV 
    try 
     ... 
    finally 
     p^ := nil; 
    end; 
    finally 
    FreeMem(p); 
    end; 
end. 
+0

謝謝雷米,很好地解釋了 –

+6

或者使用AllocMem代替GetMem + FreeMem –

+0

糾正:我的意思是GetMem + FillChar。 FreeMem仍然是AllocMem所必需的。 –