1. 程式人生 > >VC使用CRT除錯功能檢測記憶體洩漏

VC使用CRT除錯功能檢測記憶體洩漏

轉載自https://www.cnblogs.com/kex1n/archive/2011/04/27/2030753.html

方法一:

檢測記憶體洩漏的基本工具是偵錯程式和CRT除錯堆函式。為了使用除錯堆函式,在你的程式中你必須含有下面的說明:
#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include<crtdbg.h> 
      必須保證上面宣告的順序,如果改變了順序,可能不能正常工作。<crtdbg.h>的_malloc_dbg和_free_dbg將取代標準 的malloc和free函數出現在DEBUG版中,它可以跟蹤記憶體的分配和釋放。但是這隻會在DEBUG版本中發生(當#define _DEBUG的時候),而Release版本仍使用標準的malloc和free功能。

#define _CRTDBG_MAP_ALLOC表示使用CRT堆函式的相應的DEBUG版本。這個定義不是必須的,但是沒有它,記憶體洩漏報告含有的只是沒有什麼用處的資訊。

一旦你已經添加了剛才的宣告,你就能夠通過在程式中加入下面的程式碼來報告記憶體洩漏資訊:

_CrtDumpMemoryLeaks(); 



當在DEBUG模式下執行程式時,在Output視窗的Debug標籤處_CrtDumpMemoryLeaks會顯示記憶體洩漏的資訊,例如:

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,那麼記憶體洩漏報告如下顯示:

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

如果定義了_CRTDBG_MAP_ALLOC,報告的內容還包括出現分配所洩漏記憶體的檔案。在檔名之後括號內的數字是檔案內的行數值。此時雙擊包含行數值和檔名的輸出行, 或者選擇輸出行並按F4:
C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long.編輯視窗將會跳到檔案中分配所洩漏記憶體的那一行程式碼,leaktest.cpp中的行號為20的那一行。

使用_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):是一種特殊的記憶體塊,它是由MFC使用的一個物件,程式退出時,該物件的解構函式沒有被呼叫。MFC new操作符可以用來建立普通塊和客戶塊。
CRT塊(CRT block):是由C RunTime Library供自己使用而分配的記憶體塊。CRT庫自己來管理這些記憶體的分配與釋放,通常你不會在記憶體洩漏報告中發現有CRT記憶體洩漏,除非程式發生了嚴重的錯誤(例如CRT庫崩潰)。
下面這兩種型別的記憶體塊不會出現在記憶體洩漏報告中:
空閒塊(free block):已經被釋放(free)的記憶體塊。
忽略塊(Ignore block):是程式設計師顯式宣告過不要在記憶體洩漏報告中出現的記憶體塊。

設定CRT報告樣式

通常_CrtDumpMemoryLeaks()會dump記憶體洩漏的資訊到output視窗的Debug欄位。你可以使用 _CrtSetReportMode()來重新設定輸出到另一個位置。關於更詳細的如何使用_CrtSetReportMode()說明,請檢視 MSDN。

在記憶體分配數目處設定一個斷點
在記憶體洩漏報告中的檔名和行號可告訴分配洩漏的記憶體的程式碼位置,但是光是有這些資訊對於完整了解洩漏原因,有時候還是不夠的。因為一個程式在執行時,一 段分配分配記憶體的程式碼將會被呼叫很多次,但可能是某次呼叫後沒有釋放記憶體而導致了洩漏記憶體。為了確定是那些記憶體沒有被釋放,你必須不僅要知道洩漏的記憶體在那裡被分配,還要知道洩漏產生的條件。對你來說,有幫助的資訊就是記憶體分配號——在檔名和行號之後的花括號對中出現的數值。

例如,在下面的輸出資訊中,"18"是記憶體分配號,意思是洩漏的記憶體是你程式中分配的第十八個記憶體塊:
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.  

CRT庫為在程式執行期間分配的所有記憶體模組計數,包括CRT自己分配的記憶體或者諸如MFC等分配的其它模組。因此帶有分配號n的一個物件是在你的程式中分配的第n個物件,但不代表是由那段程式碼分配的第n個物件(在大部分情況下,它都不會是。)這樣的話,你可以利用分配號在記憶體分配的地方設定一個斷點。為了設定這樣一個端點,你可以在你的程式開始處,設定一個位置斷點。當你的程式在那一點break時,你就能夠從QuickWatch對話方塊或者Watch視窗設定這樣一個位置斷點。

例如,在Watch視窗中,在Name欄鍵入下面的表示式:
_crtBreakAlloc 
如果你正在用CRT庫dynamic-link library (DLL)的多執行緒版本,你必須含有上下文操作符,像這樣: 
{,,msvcrtd.dll}_crtBreakAlloc 
現在按下RETURN鍵,偵錯程式找到該值並把結果放置在value欄。如果你在記憶體分配過程中還沒有設定任何記憶體分配斷點,那麼這個值是-1。用你想要去中斷的記憶體分配的分配數值來,取代value表中的值——例如,18。

當設定完記憶體分配斷點之後,繼續除錯。這時,執行程式時一定要小心,要保證記憶體塊分配的順序不會改變。當你的程式在記憶體分配點中斷的時候,你就能夠檢視 Call Stack視窗和其他的DEBUG資訊來分析洩漏原因了。你仍然可以繼續從那一點執行程式,以至於瞭解到底發生了什麼,同時確定為什麼記憶體沒有被釋放(設 置一個記憶體分配斷點是很有幫助的)。

雖然在偵錯程式中設定記憶體分配斷點通常更容易,但是如果你喜歡的話,你也可以在你的程式碼中設定它們。為了在你的程式碼中設定一個記憶體分配斷點,可以增加這樣一行(對於第十八個記憶體分配):
_crtBreakAlloc = 18; 
你還有可以使用有相同效果的_CrtSetBreakAlloc函式:
_CrtSetBreakAlloc(18); 

方法二:比較記憶體狀態
定位記憶體洩漏的另一個方法就是在關鍵點對應用程式的記憶體狀態做快照。CRT庫提供了一個結構型別_CrtMemState。你可以用它來儲存記憶體狀態的一個快照:
_CrtMemState s1, s2, s3; 
為了在特定點對記憶體狀態進行快照,可以傳遞一個_CrtMemState結構到_CrtMemCheckpoint函式。此函式用當時記憶體狀態的一個快照來填充此結構: 
_CrtMemCheckpoint( &s1 ); 
可以通過傳遞此結構到_CrtMemDumpStatistics函式來dump _CrtMemState結構的任意點的內容: 
_CrtMemDumpStatistics( &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來定位洩漏。

*************************************************************************************/

#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include<crtdbg.h> 
int main()
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );


    Widget* pw = new Widget;
    double* p = new double(100);
    return 0;
}

Detected memory leaks!
Dumping objects ->
{55} normal block at 0x00395D28, 8 bytes long.
 Data: <      [email protected]> 00 00 00 00 00 00 59 40 
{54} normal block at 0x00395CE8, 4 bytes long.
 Data: <    > 00 00 00 00 
Object dump complete.

並沒有檔名和行數之類的資訊,只有記憶體地址,可以通過打記憶體資料斷點的方法在除錯狀態下找到記憶體洩露的地方。

由於output視窗輸出的資訊太多,檢視不便,可以將記憶體洩露等除錯資訊輸出到專門的檔案中,在WinMain()函式內部的開始處加入如下部分程式碼:

#ifdef _DEBUG
    HANDLE hLogFile = ::CreateFileA("leak.txt", GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 
        NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    _CrtSetReportMode(_CRT_WARN ,_CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, hLogFile);
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

即可。