C++中記憶體洩漏的檢查與定位
本文僅僅是一些簡短講述一下,關於C++在Windows平臺下記憶體洩漏記憶體洩漏的檢查與定位。請參閱《最快速度找到記憶體洩漏》。
在Windows平臺下,可以藉助標頭檔案<crtdbg.h>中定義的一以下幾個函式來完成。
_CrtDumpMemoryLeaks(); // 置於main函式最後,列印記憶體洩露報告 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); // 置於main函式開頭(噹噹前程式沒有統一退出點時) long _CrtSetBreakAlloc(long lBreakAlloc); //用於在給定記憶體分配操作時停止當前程式
以下是_CrtDumpMemoryLeaks()的詳細程式碼。(僅在Debug版本下有效)
#include <crtdbg.h>
void main()
{
int* pLeak = new int[10];
pLeak = nullptr;
_CrtDumpMemoryLeaks();
}
這樣在當前程式執行結束後,在Vistual Studio的Output視窗中會輸出如下資訊:
Detected memory leaks!
Dumping objects ->
c:/work/test.cpp(186) : {52} normal block at 0x003C4410, 40 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 // 用於將malloc和free函式重定向至DEBUG版本,使之能輸出對應的原始檔及行號。 #include <crtdbg.h> #ifdef _DEBUG #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) #endif // 重定向new關鍵字,否則對於所有的new操作,依舊無法正確輸出對應的原始檔及行號。 void main() { int* pLeak = new int[10]; pLeak = nullptr; _CrtDumpMemoryLeaks(); }
但有些時候,這依舊很難幫助我們找到具體的記憶體洩漏點。
我們注意到,在以上記憶體洩漏報告中有一個奇怪的資訊({52}),其表示是第52次記憶體分配時,所分配的記憶體未被回收。
方法long _CrtSetBreakAlloc(long lBreakAlloc)可以使程式停止在指定的記憶體分配操作處(即:第52次記憶體分配)。
#include <crtdbg.h>
void main()
{
_CrtSetBreakAlloc(52);
int* pLeak = new int[10];
pLeak = nullptr;
_CrtDumpMemoryLeaks();
}
再次在VS中執行上述程式碼,程式會在指定處停止並進入除錯狀態。通過檢視函式呼叫堆疊,便可以精確定位出錯的程式碼行,及當時的程式執行狀態。
在這裡,還有一點要注意的是C++的全域性變數,一些全域性變數的初始化會在進入main方法前就發生了。這種情況下,上述方法依舊無效。詳見下例:
struct MyCls
{
string SubExprStr;
};
MyCls g_MyCls = {
string("Hello world!")
};
上述程式碼中,g_MyCls物件例項的初始化在進入main方法前就完成了。在這種情況下,可以通過設定條件斷點來完成。具體如下:
// file 'dbgheap.c'
extern "C" static void * __cdecl _heap_alloc_dbg_impl(
size_t nSize,
int nBlockUse,
const char * szFileName,
int nLine,
int * errno_tmp
)
{
// ...
/* lock the heap
*/
_mlock(_HEAP_LOCK);
__try {
// ...
/* break into debugger at specific memory allocation */
if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)
_CrtDbgBreak();
在以下行處設定條件斷點,在變數lRequest等於給定記憶體分配次數時停止執行。
if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)
上述方法在本人的機器(Windows 7 VS2010)上測試有效,暫時未在其它情況下驗證過。