天天看點

Delphi記憶體管理與記憶體洩漏探析

Delphi記憶體管理與記憶體洩漏探析

楊繼宏,龔 晖,李 治

   摘  要:綜述了Delphi環境下動态記憶體配置設定與釋放的方法,分析了記憶體洩漏的可能原因,并列舉了開發“智能型遠端作業系統”過程中出現的有關記憶體洩漏的幾個執行個體。

    關鍵詞:記憶體配置設定;記憶體釋放;記憶體洩漏;智能型遠端作業系統

1  引 言

  Delphi是Borland公司的劃時代之作,以其功能強大且易學 好用而受到廣大程式員的青睐。關于Delphi的文章很多,大多數是讨論其生産的高效率、各種應用的快速實作,卻忽視了一個基本卻非常重要的問題記憶體動态 配置設定與安全釋放。Delphi應用程式開發的許多問題是由不正确的記憶體配置設定或釋放引起的,如記憶體未配置設定、未釋放、未初始化、邊界覆寫等。尤其是當自己編寫 一些元件程式時,稍不留心就會出現所謂“記憶體洩漏”的問題:某些極端的情況下,當某一問題元件被重複調用時,會大量“吃掉”機器的記憶體,導緻應用程式無法 運作甚至當機的情況,進而嚴重影響了程式的運作穩定性。是以,記憶體安全管理是每一個程式員應具備的基本技能,也是高品質運作穩定的應用程式重要标志之一。 開發一個運作穩定高品質的應用,就不可避免的遇到記憶體動态配置設定與釋放的問題。本文主要就Delphi中記憶體配置設定與釋放以及可能産生記憶體洩漏的原因展開讨 論。

2 動态記憶體配置設定與釋放

Delphi環境下,動态配置設定記憶體的方式主要有2種:

第1種是使用Delphi标準庫函數:AllocMem(GetMemor ReAllocMem,FreeMem)或New(Dispose)動态配置設定記憶體和釋放記憶體;第2種是通過調用Win32 API函數LocalAlloc和LocalFree來實作。

2.1 Delphi标準庫函數

    其函數原型如下:

    function AllocMem(Size:Cardinal):Pointer;

    procedure GetMem(var P: Pointer;Size:Integer);

    procedure ReAllocMem(var P:Pointer;Size:Integer);//重新配置設定記憶體

    procedure FreeMem(var P:Pointer[;Size:Integer]);

    procedure New(var P:Pointer);

    procedure Dispose(var P:Pointer);

調用AllocMem是向系統請求指定大小的記憶體的一種方法。如果條件允許,作業系統就會進行指定大小記憶體的配置設定,傳回值是一個指向記憶體塊的指針。函 數GetMem和ReAllocMem用法類似。差別是AllocMem函數在配置設定一個記憶體塊的同時将每個位元組初始化為0,而GetMem函數則不進行初 始化為0的操作。而ReAllocMem函數則進行記憶體的重新配置設定操作。函數New自動給指針P配置設定SizeOf(P)大小的記憶體而不需要顯式指定申請内 存的大小。

調用函數AllocMem(GetMem,ReAllocMem)申用New配置設定的記憶體。

調用AllocMem(GetMem,ReAllocMem)及FreeMem的過程如下:

    (1)首先要包含SysUtils單元檔案

    uses SysUtils

(2)在聲名了相應類型的指針ptr:^Type後,調用AllocMem函數并指定所需大小的位元組數作為參數。

Delphi記憶體管理與記憶體洩漏探析

(3)釋放記憶體,調用FreeMem并将指針作為參數,最後将指針置空。

    FreeMem(ptr);ptr:=nil;

另一對記憶體配置設定與釋放的函數New,Dispose用法與上面類似。

2.2 Win32API函數LocalAlloc和LocalFree

  函數原型如下:

Delphi記憶體管理與記憶體洩漏探析

Win32 APILocalAlloc和LocalFree函數隻适用于Win32平台,其使用參閱線上Win32 sdk幫助檔案。

無論使用哪一種方式向系統申請記憶體,在使用或釋放申請的記憶體前,應首先測試一下指向記憶體塊的指針是否有效。如果記憶體申請成功,函數傳回的指針包含一有 效位址(一個>0的長整數);否則,将傳回一個空指針nil。是以可用if(Ptr=nil)測試指針是否有效。無論使用哪一種方式申請記憶體,必須使用相 應的函數釋放記憶體。

3 記憶體洩漏原因分析

3.1 申請的記憶體沒有釋放

  這是一個基本的原因。似乎他是完全能夠避免的。但是實際情況并非如此。尤其在編寫一段比較龐大的程式時,在某個地方可能會申請一些 系統資源,但是在某些情況下可能不需要申請這些資源,是以某些粗心的程式員忘記主動去釋放他們。程式可能暫時不會出現異常,但是,如果這個函數被多次調 用,特别是在一個長時間運作的服務程式當中,他就可能會耗盡系統的記憶體資源,甚至導緻主機系統癱瘓。是以有個基本原則就是你一定要釋放你所建立的對象,讓 Delphi元件去釋放他們所建立的任何東西。

3.2 對同一個對象建立了多個執行個體,但隻釋放了某一個執行個體

  我們來看一個例子:在“智能型遠端作業系統”中,學生答題表單AnsFrm:TAnsFrm有一個按鈕BtnFrmu,點選此按鈕 調用“公式編輯器”FrmuEdit:TFrmuEdit,由于程式需要,當“公式編輯器”界面被關閉時,并不釋放其執行個體,直到關閉學生答題表單 AnsFrm時再去釋放這個執行個體。

    TansFrm設定私有變量:FrmuEdit:TFrmuEdit。點選按鈕BtnFrmu事件代碼:

Delphi記憶體管理與記憶體洩漏探析

現在n次點選按鈕BtnFrmu,則将建立n個“公式編輯器”執行個體,然而關閉表單AnsFrm時實際上隻釋放了最後一個執行個體,還有(n-1)個執行個體在記憶體中,進而造成了記憶體洩漏。

解決方法是建立新的執行個體之前先檢測是否存在某個執行個體,如果存在,則不建立。修改按鈕BtnFrmu的OnClick事件為:

Delphi記憶體管理與記憶體洩漏探析

如果FrmuEdit已經被建立,則Assigned(FrmuEdit)傳回真;反之,傳回假。現在無論點選按鈕BtnFrmu多少次,記憶體中隻有TFrmuEdit的一個執行個體,進而有效地避免了此種記憶體洩漏的發生。

3.3 用于釋放資源的代碼實際上并沒有被調用

  開發Delphi元件程式時可能會出現這種情況。Delphi是事件驅動的,事件之間有着各種聯系。元件程式開發者一定要十厘清楚 Delphi的事件機制,尤其要明白某些事件(消息)之間的先後調用關系。否則,如果你認為事件1一定會調用事件2,是以在事件2中編寫了釋放記憶體的代 碼;而事實上事件2并不調用(或有時不調用)事件1,則這段釋放記憶體的代碼實際上并不起作用,當然要造成記憶體的洩漏。筆者在開發“智能型遠端作業系統”的 “圖形/文本混合編輯器”元件程式時就遇到此種情形。

“圖形/文本混合編輯器”TEditEx=class(TCustomControl),采用多緩沖技術,TEditEx有私有成員:GraphDC,TextDC,BuffDC:HDC(圖形緩沖,文本緩沖和混合緩沖裝置環境句柄);裝置環境初始化:

Delphi記憶體管理與記憶體洩漏探析

注意到TEditEx是從TCustomControl繼承的,不是一個窗體類,故其執行個體被關閉時不會觸發WM_CLOSE 消息,即不調用這部分代碼去釋放申請的資源;也就是說在關閉這個執行個體後,向系統申請的這部分資源(約2 MB)仍然在記憶體中。學生在答題時會頻繁的用到“圖形/文本混合編輯器”,即頻繁的建立TEditEx的一個執行個體,然後關閉他,但是卻不釋放所申請的資 源。在使用“圖形/文本混合編輯器”4~6次(和機器有關)後,系統資源接近枯竭而不得不關閉主程式甚至要重新開機機器。

    解決方法就是在銷毀器中釋放這些資源:

Delphi記憶體管理與記憶體洩漏探析

3.4 出現異常時沒有釋放記憶體

  程式運作時難免發生異常,如果此處恰好存放了釋放資源的代碼,則申請的資源将得不到釋放,進而産生一種記憶體洩漏。采用Delphi 的異常處理技術,可以確定申請資源的安全釋放。Delphi支援如下2種異常處理:try…except和try…finally。其文法分别是:

try statements except exceptionBlock end和trystatementList1 finally statementList2 end。

這2種異常處理是有差別的:當系統資源被配置設定後,如果希望僅當在出現程式異常時釋放資源,一般應使用try…except;如果不管程式在執行程式塊 時是否出現異常均希望釋放資源,一般使用try…finally。切忌濫用異常處理結構,否則,将人為地降低程式的性能。  申請記憶體或建立記憶體對象以 後,将可能産生異常的代碼放在try部分,一旦異常發生,無論是try部分的哪一行産生的,程式均會跳轉到異常處理代碼段(except或finally 部分),在這部分存放釋放資源的代碼,以保證申請資源的釋放。正确使用Delphi提供異常處理技術,無論程式在運作時出現何種異常,都能保證申請資源的 釋放。

4 結 語

  記憶體的動态配置設定與安全釋放,對于應用的穩定、主機系統的安全非常重要。要對程式中各種對象(這裡的對象是泛指那些占 用系統資源的變量(包括各種基本類型變量和類執行個體對象變量等))的生存(包括從配置設定到釋放)有完全清楚的認識,杜絕記憶體洩漏的發生,開發出健壯、高效的應 用程式。

轉載于:https://www.cnblogs.com/Handll/archive/2011/11/10/2244944.html

繼續閱讀