使用Microsoft Visual C++來檢測和隔離記憶體洩漏 (轉)

worldblog發表於2007-12-04
使用Microsoft Visual C++來檢測和隔離記憶體洩漏 (轉)[@more@]使用 Visual C++來檢測和隔離洩漏

作者: Edward Wright
日期: 2000年05月24日

簡介

具有動態的分配和釋放記憶體的能力是C/C++語言的重要特色之一,但是中國的哲人孫子指出,最強有力的也是最脆弱的。對C/C++應用程式來說這當然是正確的,記憶體管理錯誤通常是起源之一。非常微妙且難於檢測的bug之一就是記憶體洩漏——不能正確地去分配已經分配了的記憶體。一個僅僅發生一次的輕微記憶體洩漏不可能引起注意,但是洩漏了大量記憶體或者日益增多的洩漏的程式可能表現出徵兆,從可憐的(和慢慢地減少)到記憶體不足而完全失靈。更壞的是,一個有洩漏的程式可能佔用很多的記憶體以至於導致另一個程式失靈,留給的只是對問題的一無所知。此外,一個嚴重的記憶體洩漏甚至可能是其他問題的徵兆。

幸運的是,Visual C++ debugger 和 CRT庫提供給你一系列有效的檢測和鑑定記憶體洩漏的工具。這片文章闡述瞭如何使用這些工具去有效並的隔離記憶體洩漏。

設定記憶體洩漏檢測

檢測記憶體洩漏的基本工具是器和CRT除錯堆。為了使用除錯堆函式,在你的程式中你必須含有下面的說明:

#define _CRTG_MAP_ALLOC #include #include


#include說明必須按順序說明。如果你改變了順序,你所用的函式可能不能正常工作。包含crtdbg.h的_malloc_dbg_free_dbgmallocfree函式對映到測試版中,它可以跟蹤記憶體的分配和釋放。這種對映僅僅在一個測試體系中發生(也就是說,僅僅當_DEBUG被定義的時候)。釋放的體系使用通常的mallocfree功能。

#define說明對映CRT堆函式的低階版本到相應的測試版本。這個說明是不需要的,但是沒有它,記憶體洩漏處含有的只是沒有多大用處的資訊。

一旦你已經增加了剛才的說明,你能夠透過在你的程式中包含下面的說明來釋放記憶體資訊:

_CrtDumpMemoryLeaks();


當你在除錯情況下執行你的程式時,在輸出視窗的Debug 標籤處_CrtDumpMemoryLeaks表現出記憶體洩漏的資訊。記憶體洩漏資訊類似下面這樣:

Detected memory leaks!

Dum s ->

C:PROGRAM FILESMyProjectsleaktestleaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long.

Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

如果你沒有用#define _CRTDBG_MAP_ALLOC說明,記憶體堆存處類似下面這樣:

Detected memory leaks!

Dumping objects ->

{18} normal block at 0x00780E80, 64 bytes long.

Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

像你所知道的,當_CRTDBG_MAP_ALLOC被定義時,_CrtDumpMemoryLeaks給了你更多的有用資訊。如果_CRTDBG_MAP_ALLOC沒有被定義,那麼將向你如下顯示:

  • 記憶體分配數值(花括號內)
  • 模組的型別(normal、client或者CRT)
  • 以十六進位制格式定位的記憶體
  • 以位元組計模組的大小
  • 第一個十六位元組的內容(也可以用十六進位制)

當定義了_CRTDBG_MAP_ALLOC的時候,顯示的內容也向你展現了出現洩漏記憶體所分配地方的。在檔名之後括號內的數字(20,以此為例)是檔案內的行數值。如果你雙擊包含行數值和檔名的輸出行,

C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long.

指標將會跳到原始檔中記憶體被分配地方的行(在上面的情況下,leaktest.cpp的行號為20)。選擇輸出行並按F4將有同樣的效果。

使用_CrtSetDbgFlag

如果你的程式總是在同一各地方存在,那麼_CrtDumpMemoryLeaks時非常容易的。但是,如果你的程式需要在多個位置退出該怎麼辦?在每一個可能的出口處如果不呼叫_CrtDumpMemoryLeaks,你可在你的程式開始處包含下面的呼叫:

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

當你的程式退出時,這個說明自動地呼叫_CrtDumpMemoryLeaks。你必須設定兩個位域,_CRTDBG_ALLOC_MEM_DF_CRTDBG_LEAK_CHECK_DF,像以前說明的一樣。

翻譯記憶體模組的型別

像早期宣告的一樣,記憶體洩漏資訊鑑別洩漏記憶體的每一個模組作為一個普通的模組、一個客戶模組或者一個CRT模組。實際上,普通的模組和客戶模組是你可能留心的唯一型別。

  • 一個普通模組(normal block)是由你的程式分配的普通記憶體。
  • 一個客戶模組(client block)是一種特殊的記憶體模組,它由於需要一個解構函式的而被Microsoft Foundation Classes (MFC)所使用。MFC new操作子建立一個普通模組或者一個客戶模組,來適合被建立的模組。
  • 一個CTR模組是由CRT庫提供自己使用而分配的記憶體模組。CRT庫對這些模組來管理自己的去分配,因此你不可能在記憶體洩漏報告中注意到這些,除非有些地方有嚴重的錯誤(例如,CRT庫崩潰)。

在記憶體洩漏資訊中有兩種你從來沒有見過的模組型別:

  • 空閒模組(free block)是一種被釋放的記憶體模組
  • Ignore block是你已經特殊標記過以至於在記憶體洩漏報告中不會出現的模組。

設定CRT報告樣式

像以前描寫的一樣,按預設方式,_CrtDumpMemoryLeaks傾卸記憶體洩漏資訊到輸出視窗的Debug窗格。你可以運用_CrtSetReportMode重新設定它到堆存處,到另一個位置。如果你使用一個庫,它可能重新設定輸出到另一個位置。在這種情況下,你能夠利用下面的說明來設定輸出位置回到輸出視窗:

_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

關於使用_CrtSetReportMode去傳送輸出資訊到另一個位置,要看Visual C++檔案的_CrtSetReportMode節。

在記憶體分配數目處設定一個斷點

在記憶體洩漏報告中的檔名和行號可告訴你洩漏的記憶體在那裡被分配,但是瞭解記憶體在那裡分配對於鑑定問題不總是充分的。在一個程式執行過程中,經常是一個分配將會被呼叫很多次,但是它可能在某次呼叫中洩漏記憶體。為了確定問題,你必須不但知道洩漏的記憶體在那裡分配,還要知道洩漏發生的條件。對你來說,使它成為可能的那條資訊是記憶體分配號。當那些被顯示的時候,檔名和行號之後,這是在curly brace中出現的數值。例如,在下面的輸出中,“18”是記憶體分配號。它的意思是洩漏的記憶體是你程式中記憶體分配的第十八個模組。

Detected memory leaks!

Dumping objects ->

C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long.

Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

CRT庫計算在程式執行期間分配的所用記憶體模組,包括CRT自己分配的記憶體或者諸如MFC的其它模組。因此帶有分配號n的一個物件是在你的程式中分配的第n個物件,但不可能是由程式碼分配的第n個物件。(在大部分情況下,它是不會的。)

你可以利用分配號在記憶體分配的地方設定一個斷點。為了做這些,你可以距離你的程式開始很近處,設定一個位置斷點。當你的程式在那一點暫停時,你能夠從QuickWatch對話方塊或者Watch視窗設定這樣一個位置斷點。例如,在Watch視窗中,在Name欄鍵入下面的:

_crtBreakAlloc

如果你正在用CRT庫的多執行緒的dynamic-link library (DLL)版本,你必須含有上下文運算子,像這裡說明的:

{,,msvcrtd.dll}_crtBreakAlloc

現在,按RETURN。偵錯程式評估呼叫並且把結果放置在Value欄。如果你在記憶體分配過程中還沒有設定任何斷點,那麼這個值是-1。使用你想中斷處記憶體分配的分配數值來代替Value表中的值——例如,18 去中斷早期在輸出過程中展現的分配.

當你在你感興趣的記憶體分配處設定斷點之後,你能夠繼續除錯。在與從前相同的條件下,執行程式時一定要小心,因而分配的順序不會改變。當你的程式在一個特殊的記憶體分配點中斷的時候,你能夠檢視Call Stack視窗和其他的測試資訊來確定在此條件下記憶體的分配。如果需要的話,你可以繼續從那一點程式,以至於瞭解物件到底發生了什麼事,同時還可能確定為了沒有正確地被去分配。(對物件設定一個資料斷點是很有幫助的。)

雖然在偵錯程式中設定記憶體分配斷點通常更加容易,但是如果你喜歡的話,你可以在你的程式碼中設定它們。為了在你的程式碼中設定一個記憶體分配斷點,可以增加這樣一行(對於第十八個記憶體分配):

_crtBreakAlloc = 18;

最為一個選擇,你可以使用有相同效果的_CrtSetBreakAlloc函式。

_CrtSetBreakAlloc(18);

比較記憶體狀態

定位記憶體洩漏的另一個方法就是在關鍵點對應用程式的記憶體狀態做快照。CRT庫提供了一個結構型別,_CrtMemState。你可以使用它來記憶體狀態的一個快照。

_CrtMemState s1, s2, s3;

為了在特定點對記憶體狀態進行快照,可以傳遞一個_CrtMemState結構到he _CrtMemCheckpoint函式。此函式用當時記憶體狀態的一個快照來填充此結構:

_CrtMemCheckpoint( &s1 );

你可以透過傳遞此結構到_CrtMemDumpStatistics函式來傾卸_CrtMemState結構的任意點的內容:

_CrtMemDumpStatistics( &s3 );( &s1 );

此函式列印出類似於下面這樣的一堆記憶體分配資訊:

0 bytes in 0 Free Blocks.

0 bytes in 0 Normal Blocks.

3071 bytes in 16 CRT Blocks.

0 bytes in 0 Ignore Blocks.

0 bytes in 0 Client Blocks.

Largest number used: 3071 bytes.

Total allocations: 3764 bytes.

為了確定一個記憶體洩漏是否在一節程式碼中出現,你可以在此節前和此節後對記憶體狀態作快照,然後用_CrtMemDifference比較兩種狀態:

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

 

if ( _CrtMemDifference( &s3, &s1, &s2) )

 _CrtMemDumpStatistics( &s3 );

像名字暗示的一樣,_CrtMemDifference比較兩個記憶體狀態(最先的兩個引數)並且產生一個不同於這兩個狀態的結果(第三個引數)。在你的程式開始和結尾處的_CrtMemCheckpoint呼叫和使有_CrtMemDifference來比較結果為檢測記憶體洩漏提供了另一種方法。如果一個洩漏被檢測到,那麼可以使用_CrtMemCheckpoint呼叫來分割你的程式並且使用二元binary search technique來定位洩漏。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988193/,如需轉載,請註明出處,否則將追究法律責任。

相關文章