2012-05-26 64 views
11

根據FastMM4,目前我正在處理的Delphi程序正在泄漏很多字符串。 AnsiStrings準確地說:Delphi應用程序泄露AnsiStrings

enter image description here

應用(http://sourceforge.net/projects/orwelldevcpp/)用於泄露了很多其他數據類型,但FastMM4可以報告實例被創建在哪裏,所以我設法解決這個問題。奇怪的是,FastMM4根本沒有報告這些泄漏的位置。

編輯:看來它畢竟看到修復的答案。無論如何,這個問題依然存在:我在這個世界上如何泄漏這些東西?

所以,EHM,不幸的是,我有不知道要尋找什麼。我的意思是,如果這些東西超出範圍,它們應該被自動釋放(即使它們在堆上)?

我還是設法追蹤幾泄漏下降隨機評論,看會發生計數什麼。這裏有一個例子:

// simply passing it a constant creates a leak... 
MainForm.UpdateSplash('Creating extra dialogs...'); 

procedure TMainForm.UpdateSplash(const text : AnsiString); 
begin 
    if not devData.NoSplashScreen then // even if this branch is NOT taken 
    SplashForm.Statusbar.SimpleText := 'blablabla' + text; 
end; 

// And even if the function call itself is placed within a NOT taken branch! 

這裏有一個泄漏的另一個例子:

// Passing this constants produces leaks... 
procedure TCodeInsList.AddItemByValues(const a, b, c: AnsiString;...); 
var 
    assembleditem : PCodeIns; 
begin 
    new(assembleditem); 
    assembleditem^.Caption:=a; 
    assembleditem^.Line:=b; 
    assembleditem^.Desc:=c; 
    ... 
    fList.Add(assembleditem); 
end; 

// ... even when calling this on WM_DESTROY! 
destructor TCodeInsList.Destroy; 
var 
    I: integer; 
begin 
    for I := 0 to fList.Count - 1 do 
    Dispose(fList[I]); 
    fList.Free; 
    inherited Destroy; 
end; 

// produces leaks!? 

有相當一堆串泄漏問題在這裏,但沒有真正澄清一個應該是什麼模式爲。 Google也不提供。

編輯:所以,我必須尋找通過常量。但爲什麼?

所以EHM,任何想法?

+0

我目前無法加載sourceforge項目。有沒有可能的主要形式沒有被正確銷燬,從而留下懸掛的字符串?會這樣做嗎? –

+0

delphi版本?如果可以的話,使用aqtime進行測試,它會告訴你究竟在哪裏泄漏。 –

+0

@RichardA:正如你可以在source \ devcpp.dpr中看到的,splashform是使用'Free'釋放的。將嘗試添加caFree到OnClose事件。 @沃倫:我非常懷疑aqtime能告訴我比FastMM4,gpProfiler和MemCheck更多。我也需要升級到XE的aqtime(現在使用D7)。我的大學確實有一個D2009許可證(但不是像我這樣的EE人),但aqtime甚至不支持它看起來。 – Orwell

回答

14

您不需要明確分配字符串。除了引用計數之外,對象或記錄的字符串字段也可能會泄漏。例如,

type 
    PRecord = ^TRecord; 
    TRecord = record 
    S: string; 
    end; 

procedure TForm1.Button4Click(Sender: TObject); 
var 
    r: PRecord; 
begin 
    GetMem(r, SizeOf(r^)); 
    Initialize(r^); 
    r.S := ' '; 
    FreeMem(r); 

在上面的例子中,由於記錄本身的內存被釋放,FastMM將只報告泄漏的字符串。


在任何情況下,FastMM不在對話框中顯示堆棧軌跡並不表示它缺少該信息。請務必在'FastMM4Options.inc'中定義FullDebugMode,LogMemoryLeakDetailToFileLogErrorsToFile。然後在可執行文件的目錄中查找'[ExecutableName] _MemoryManager_EventLog.txt'文件。

對於上面的例子,FastMM生成以下文件:

 
--------------------------------2012/5/27 4:34:46-------------------------------- 
A memory block has been leaked. The size is: 12 

Stack trace of when this block was allocated (return addresses): 
40305E 
404B5D 
404AF0 
45C47B 
43D726 
42B0C3 
42B1C1 
43D21E 
76C4702C [GetWindowLongW] 
77AE3CC3 [Unknown function at RtlImageNtHeader] 

The block is currently used for an object of class: Unknown 

The allocation number is: 484 

Current memory dump of 256 bytes starting at pointer address 7EF8DEF8: 
01 00 00 ... 
... 

現在可以運行應用程序,暫停,然後搜索地址。對於上述日誌和測試應用程序,地址解析爲:

 
Stack trace of when this block was allocated (return addresses): 
40305E -> _GetMem 
404B5D -> _NewAnsiString 
404AF0 -> _LStrAsg 
45C47B -> TForm1.Button4Click (on FreeMem line) 
43D726 -> TControl.Click 
... 


編輯: 不用手動查找地址,生成通過鏈接器選項的詳細地圖文件和FastMM將做到這一點(感謝梅森的評論)。


您對該問題的編輯反映了一個非常類似於上面示例中的泄漏。如果'fList'是一個普通的TList,它只是保存指針,並且不知道這些指針指向什麼。因此,當你處理指針時,只有分配給指針本身的內存被釋放,而不是記錄的字段。所以泄漏無關傳遞給函數的常數,而是像下面的模式:

對於要設置在記錄,代碼應強制轉換的指針,它的類型:

Dispose(PCodeIns(p)); 

所以你的「TCodeInsList.Destroy」應該是:

destructor TCodeInsList.Destroy; 
var 
    I: integer; 
begin 
    for I := 0 to fList.Count - 1 do 
    Dispose(PCodeIns(fList[I])); 
    fList.Free; 
    inherited Destroy; 
end; 


最後,你要尋找的模式似乎在尋找地方的代碼的意圖自由RECO具有字符串字段的rds(不太可能的對象)。尋找Dispose,有點不太可能FreeMem,甚至不太可能FreeInstance釋放記憶FastMM顯示分配的內存泄漏的對象/記錄可能會有所幫助。

+0

嗯,當我尋找其他泄漏時,我一定忽略了它,但是,是的,你說得對,FastMM4確實顯示了一些信息:102DF8 [SynEditKeyCmds] [SynEditKeyCmds] [@ GetMem]。將其乘以40000,在150MiB文本文件中獲得該想法。謝謝,將會研究它。 – Orwell

+1

順便說一句,如果你告訴鏈接器生成一個詳細的地圖文件,那麼這個作業會變得更容易。然後FastMM可以爲你做查找。 –

+0

@Mason - 謝謝,我想知道我在測試應用程序中缺少什麼。 :) –

4

你說得對,應該自動清理字符串。不過,我已經看到了一些方法來解決這個問題。

第一個是如果你正在做的事情爲字符串數據結構直接,可以打破的引用計數。這是最有可能的,你正在泄漏的字符串數量。

另一個調用暫停並在堆棧上保留字符串引用。但是你不會在堆棧中留下40,000個字符串引用,所以我會尋找通過字符串傳遞的代碼,然後用它的引用計數來擺弄。

+0

我百分之百確定我沒有在任何地方使用Halt。我經常使用Exit,但那應該不重要。而且,嗯,不,我沒有搞亂第零指數。 – Orwell

+4

@Orwell'exit'可以安全地在任何地方使用:它將轉到爲任何本地字符串生成的隱藏的'try..finally'塊,並按預期處理引用計數。 –

1

簡而言之,Delphi內建的字符串類型是引用計數。內存分配和處理方法不會負責更新引用計數,因此編譯器不知道實際上可以釋放記錄中的字符串。

不鼓勵使用引用計數字符串類型定義記錄。我之前也有同樣的困惑。如果你看看Delphi庫的來源。你會發現許多記錄有PChar不是字符串。

Some discuss about records

1

泄漏字符串最常見的方式就是有一個包含字符串和一個指向記錄的記錄。如果您只是簡單地執行該指針的Dispose(),編譯器將只釋放指針,而不是下一個記錄中的所有內容。一定要確保你的配置代碼告訴編譯器你正在處理什麼。

例如,假設在TTreeView中,我將Node.Data中的PMyRecord =^MyRecord。如果在最後循環所有節點並簡單地執行Dispose(Node.Data),那麼MyRecord中的任何字符串將不會被正確處理。在這種情況下,您需要Dispose(PMyRecord(Node.Data))。