設置內存泄漏檢測
檢測內存泄漏的基本工具是調試器和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結構到he _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來定位泄漏。