檢測並排除記憶體洩漏 (轉)

amyz發表於2007-08-15
檢測並排除記憶體洩漏 (轉)[@more@]

 摘要:
 本文描述瞭如何使用VC++和CRT庫提供的工具定位和排除洩漏,檢測的難度使得使用C/C++語言的應用開發產生問題。
 介紹:
 動態分配、回收記憶體是C/C++程式語言一個最強的特點,但是中國哲學家孫(Sun Tzu,我不知道是誰?那位知道?) 指出,最強的同時也是最弱的。這句話對C/C++應用來說非常正確,在記憶體處理出錯的地方通常就是S產生的地方。一個最敏感和難檢測的BUG就是記憶體洩漏-沒有把前邊分配的記憶體成功釋放,一個小的記憶體洩漏可能不需要太注意,但是洩漏大塊記憶體,或者漸增式的洩漏記憶體可能引起的現象是:先是低下,再就是引起復雜的記憶體耗盡錯誤。最壞的是,一個記憶體洩漏程式可能用完了如此多的記憶體以至於引起其他的程式出錯,留給的是不能知道錯誤到底來自哪裡。另外,一個看上去無害的記憶體洩漏可能是另一個問題的先兆。幸運的是VC++DEBUGER和CRT庫提供了一組有效的檢測和定位記憶體洩漏的工具。本文描述如何使用這些工具有效和的排除記憶體洩漏。

 啟動記憶體洩漏檢測:
 主要的檢測工具是DEBUGER和CRT堆除錯。要使除錯函式生效,必須要在你的程式中包含以下幾個語句:
#define _CRTG_MAP_ALLOC
#include
#include
 並且這些#include 語句必須按上邊給出的順序使用。如果你改變了順序,可能導致使用的函式工作不正常。包含crtdbg.h的作用是用malloc和free函式的debug版本(_malloc_dbg 和 _free_dbg)來替換他們,他們能跟蹤記憶體分配和回收。這個替換僅僅是在debug狀態下生效,Relese版本中還是使用普通的malloc和free函式。
 上面的#define語句使用crt堆函式相應的debug版本來替換正常的堆函式。這個語句不是必需的,但是沒有他,你可能會失去一些有用的記憶體洩漏資訊。
 你一旦在你的程式中增加了以上的語句,你可以透過在程式中增加_CrtDumpMemoryLeaks();函式來輸出記憶體洩漏資訊。
 當你在debuger下執行你的程式時,_CrtDumpMemoryLeaks 顯示記憶體洩漏資訊在OutPut視窗的Debug標籤項裡。記憶體洩漏資訊舉例如下:

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 的情況下,顯示資訊包含:
1.記憶體分配的編號(大括弧中的數字);
2.記憶體快的型別(普通型、客戶端型、CRT型);
3.16進製表示的記憶體位置;
4.記憶體快的大小;
5.前16bytes的內容。
 如果定義了_CRTDBG_MAP_ALLOC ,輸出資訊還包含當前洩漏記憶體是在那個中被分配的定位資訊。檔名後圓括弧中的數字是行數。如果你雙擊這行資訊,
C:PROGRAM FILESVISUAL STUDIOMyProjectsleaktestleaktest.cpp(20) : {18}
  normal block at 0x00780E80, 64 bytes long.
游標就會跳轉到原檔案中分配這個記憶體的行前。選擇Output中的題是行,按F4能達到同樣的效果。

 使用Using _CrtSetDbgFlag:
 如果你的程式的退出點只有一個的話,_CrtDumpMemoryLeaks將是非常容易。但是,如果你的程式有多個退出點話會是什麼樣一個情況?如果不想在每個退出點都呼叫_CrtDumpMemoryLeaks,你可以在程式的開始包含以下呼叫:
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
這個語句會在你的程式結束時自動呼叫_CrtDumpMemoryLeaks,但是你必須象前邊提到的那樣設定_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF這兩個標誌位。

 介紹一下記憶體塊的型別:
 就象前面指出的,一個記憶體洩漏資訊指出每個記憶體洩漏塊的型別為普通、客戶端或者CRT型。在實際程式中,普通型和客戶端型式最常見的型別。
 普通型記憶體塊是你的程式平常分配的記憶體型別。
 客戶端型記憶體塊是MFC程式給需要析構的分配的記憶體塊。MFC的new操作可以選擇普通型或客戶端型中合適的一種作為將要被建立的物件的記憶體塊型別。
 CRT記憶體塊是CRT庫為自己使用而分配的記憶體塊。CRT在處理自己的釋放記憶體操作時使用這些塊,所以在記憶體洩漏報告中這種型別並不常見,除非發生嚴重異常(例如:CRT庫出錯)。
 還有兩種型別你在記憶體洩漏資訊中看不到:
 自由塊,它是已經被釋放的記憶體塊;
 忽略塊,它是已經被特殊標示的記憶體塊。

 設定CRT報告的格式:
 在預設情況下,_CrtDumpMemoryLeaks輸出的記憶體洩漏資訊就象前邊描述的那樣。你可以使用_CrtSetReportMode讓這些輸出資訊輸出到其他地方。如果你使用一個庫,它可能要使輸出資訊到其他的地方,在這種情況下,你可以使用_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );語句使輸出資訊重新定位到Output視窗。

 根據記憶體分配編號設定斷點:
 記憶體洩漏報告中的檔名和行數告訴你記憶體洩漏的位置,但是知道記憶體洩漏位置不是總是能找到問題所在。在一個執行的程式中一個記憶體分配操作可能被呼叫多次,但是記憶體洩漏可能只發生在其中的某次操作中。為了確認問題所在,你除了知道洩漏的位置之外,你還必須要知道發生洩漏的條件。記憶體分配編號使得解決這個問題成為可能。這個數字就在檔名、行數之後的大括弧內。例如,在上面的輸出中“18”就是記憶體分配編號,它的意思是你程式中的記憶體洩漏發生在第18次分配操作中。
 CRT庫對正在執行程式中所有的記憶體塊分配進行計數,包括自身的記憶體分配,或者其他庫(象MFC)。一個物件的分配編號是n表示第n個物件被分配,但是它可能並不表示第N個物件透過程式碼被分配(在大多數情況下它們並不相同)。
 你可以根據記憶體分配編號在記憶體被分配的位置設定斷點。先在程式開始部分附近設定一個斷點,當你的程式在斷點處停止後,你可以透過QuickWatch對話方塊或者Watch視窗來設定記憶體分配斷點。在Watch視窗中的Name列中輸入_crtBreakAlloc,如果你使用的是多執行緒DLL版本的CRT庫的話你必須包含上下文轉換 {,,msvcrtd.dll}_crtBreakAlloc。完成後按回車,debugger處理這次呼叫,並且把返回值顯示在Value列中。如果你沒有設定記憶體分配斷點的話返回值是-1。在Value列中輸入你想設定的分配數,例如18。
 你在自己感興趣的記憶體分配位置設定斷點後,你可以繼續debugging。細心的執行你的程式在相同的條件下,這樣才能保證記憶體分配的順序不致發生變化。當程式在特定的記憶體分配處停下來後, 你可以檢視Call 視窗和其他的debugger資訊來分析此次記憶體分配的條件。如果有必要你可以繼續執行程式,看一看這個物件有什麼變化,或許可以得知為什麼記憶體沒有被正確的釋放。
 儘管這個操作非常容易,但是如果你高興的話也可以在程式碼中設定斷點。在程式碼中增加一行程式碼_crtBreakAlloc = 18;另外也可以透過_CrtSetBreakAlloc(18)來完成設定。

 比較記憶體狀態
 另一個定位記憶體洩漏的方法是在重要位置捕捉應用程式的“記憶體快照”。CRT庫提供了一個結構體型別 _CrtMemState,使用它你可以儲存記憶體狀態的快照(當前狀態)。
_CrtMemState s1, s2, s3;
 為了得到一個快照,可以把一個_CrtMemState 結構體傳給_CrtMemCheckpoint 函式,這個函式可以把當前的記憶體狀態填充在結構體中:
_CrtMemCheckpoint( &s1 );
 你可以透過把結構體_CrtMemState 傳給_CrtMemDumpStatistics函式來輸出結構體中的內容。
_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把程式分成兩半分別使用上述方法來檢測記憶體洩漏,這樣就是使用二分法來檢查記憶體洩漏。

 

 


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

相關文章