VC內存泄露檢測

介紹:

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

啓動內存泄漏檢測:
主要的檢測工具是debuger和crt堆除錯函數。要使除錯函數生效,必須要在你的程序中包含以下幾個語句:

#define _crtdbg_map_alloc
#include "stdlib.h"
#include "crtdbg.h"

並且這些#include 語句必須按上邊給出的順序使用。如果你改變了順序,可能導致使用的函數工作不正常。包含crtdbg.h的作用是用malloc和free函數的debug版本(_malloc_dbg 和 _free_dbg)來替換他們,他們能跟蹤內存分配和回收。這個替換僅僅是在debug狀態下生效,relese版本中還是使用普通的malloc和free函數。
上面的#define語句使用crt堆函數相應的debug版本來替換正常的堆函數。這個語句不是必需的,但是沒有他,你可能會失去一些有用的內存泄漏信息。

你一旦在你的程序中增加了以上的語句,你可以通過在程序中增加_crtdumpmemoryleaks();函數來輸出內存泄漏信息。

//呵呵感謝網友提出問題,由於是轉的貼,未經作者同意不便修改,所以有異議的地方我會註明的_crtdumpmemoryleaks(); 大小寫有問題 都要改成如下_CrtDumpMemoryLeaks();  --by帥得不敢出門



當你在debuger下運行你的程序時,_crtdumpmemoryleaks 顯示內存泄漏信息在output窗口的debug標籤項裏。內存泄漏信息舉例如下:

detected memory leaks!
dumping objects ->
c:\program files\visual studio\myprojects\leaktest\leaktest.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 files\visual studio\myprojects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780e80, 64 bytes long.

光標就會跳轉到原文件中分配這個內存的行前。選擇output中的題是行,按f4能達到同樣的效果。

// 我在VC6.0測試下#define _crtdbg_map_alloc這個大小寫也有問題應該爲

CRTDBG.H 中的inline void* __cdecl operator new(unsigned int s)
{ return ::operator new(s, _NORMAL_BLOCK, __FILE__, __LINE__); }

  --by帥得不敢出門 



使用using _crtsetdbgflag:
如果你的程序的退出點只有一個的話,調用_crtdumpmemoryleaks將是非常容易。但是,如果你的程序有多個退出點話會是什麼樣一個情況?如果不想在每個退出點都調用_crtdumpmemoryleaks,你可以在程序的開始包含以下調用:

_crtsetdbgflag( _crtdbg_alloc_mem_df | _crtdbg_leak_check_df);
這個語句會在你的程序結束時自動調用_crtdumpmemoryleaks,但是你必須象前邊提到的那樣設置_crtdbg_alloc_mem_df 和 _crtdbg_leak_check_df這兩個標誌位。

上面這句大小寫有問題的 改成_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 而且包含了CRTDBG.H 頭文件,而這個頭文件中已有

#define _CRTDBG_ALLOC_MEM_DF        0x01  /* Turn on debug allocation */
#define _CRTDBG_DELAY_FREE_MEM_DF   0x02  /* Don't actually free memory */就無需再自己設置上面說的那兩個標誌位了

  --by帥得不敢出門 



介紹一下內存塊的類型:
就象前面指出的,一個內存泄漏信息指出每個內存泄漏塊的類型爲普通、客戶端或者crt型。在實際程序中,普通型和客戶端型式最常見的類型。

普通型內存塊是你的程序平常分配的內存類型。

客戶端型內存塊是mfc程序給需要析構的對
象分配的內存塊。mfc的new操作可以選擇普通型或客戶端型中合適的一種作爲將要被創建的對象的內存塊類型。

crt內存塊是crt庫爲自己使用而分配的內存塊。crt在處理自己的釋放內存操作時使用這些塊,所以在內存泄漏報告中這種類型並不常見,除非發生嚴重異常(例如:crt庫出錯)。

還有兩種類型你在內存泄漏信息中看不到:

自由塊,它是已經被釋放的內存塊;
忽略塊,它是已經被特殊標示的內存塊。

設置crt報告的格式:
在默認情況下,_crtdumpmemoryleaks輸出的內存泄漏信息就象前邊描述的那樣。你可以使用_crtsetreportmode
 
//其實是_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

爲了得到一個快照,可以把一個_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把程序分成兩半分別使用上述方法來檢測內存泄漏,這樣就是使用二分法來檢查內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章