?
1、泄露代碼示例
void X::SetApplicationBtn()
{CMFCRibbonApplicationButton* pBtn = GetApplicationButton();// 獲取 Ribbon Bar 指針// 創建自定義按鈕CCustomRibbonAppButton* pCustomButton = new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26);// 替換默認的 Application Buttonthis->SetApplicationButton(pCustomButton, CSize(45, 45));
}
2、CRT?
3、使用CRT調試功能來檢測內存泄漏
筆記小結
1.1 使用方式
// 使用調試堆函數:#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include<crtdbg.h>// 檢測內存泄漏的地方添加下面這條語句來輸出內存泄漏信息
_CrtDumpMemoryLeaks();
1.2 相關介紹
// 分配泄漏內存的文件名,以及文件名后括號中的數字表示發生泄漏的代碼行號
#define _CRTDBG_MAP_ALLOC
1.2-1?內存泄露提示(和上述內存泄露代碼無關)
Detected memory leaks!
Dumping objects ->
D:\a\_work\1\s\src\vctools\crt\github\stl\src\locale0.cpp(34) : {91690} crt block at 0x0000020FDD50EF20, subtype 0, 16 bytes long.Data: < P > 00 00 00 00 00 00 00 00 90 F2 50 DD 0F 02 00 00
1.3 內存塊類型
normal(普通)、client(客戶端)、?CRT(運行時)
normal block(普通塊):這是由你的程序分配的內存。
client block(客戶塊):這是一種特殊類型的內存塊,專門用于 MFC 程序中需要析構函數的對象。MFC new 操作符視具體情況既可以為所創建的對象建立普通塊,也可以為之建立客戶塊。
CRT block(CRT 塊):是由 C RunTime Library 供自己使用而分配的內存塊。由 CRT 庫自己來管理這些內存的分配與釋放,我們一般不會在內存泄漏報告中發現 CRT 內存泄漏,除非程序發生了嚴重的錯誤(例如 CRT 庫崩潰)。
1.4? 輸出泄露
// 無論程序在什么地方退出都會自動調用 _CrtDumpMemoryLeak
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
1.5 內存泄露信息?輸出定向到別的地方
//
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );
4、設置斷點
內存分配序號:件名和行號之后的花括弧里的那個數字。
????????CRT 庫對程序運行期間分配的所有內存塊進行計數,包括由 CRT 庫自己分配的內存和其它庫(如 MFC)分配的內存。因此,分配序號為 N 的對象即為程序中分配的第 N 個對象,但不一定是代碼分配的第 N 個對象。
????????方法是在程序起始附近設置一個位置斷點。當程序在該點中斷時,可以從 QuickWatch(快速監視)對話框或 Watch(監視)窗口設置一個內存分配斷點。
// Watch 窗口中,在 Name 欄鍵入下面的表達式:_crtBreakAlloc
?Watch 窗口中,在 Name 欄鍵入下面的表達式:
如果要使用 CRT 庫的多線程 DLL 版本(/MD 選項),那么必須包含上下文操作符,像這樣:
?
{,,msvcrtd.dll}_crtBreakAlloc
現在按下回車鍵,調試器將計算該值并把結果放入 Value 欄。如果沒有在內存分配點設置任何斷點,該值將為 –1。
5、MFC多文檔使用示例
直接使用_CrtSetDbgFlag | Microsoft Learn
BOOL X::InitInstance()
{// 啟用自動內存泄漏檢測(調試模式)
#ifdef _DEBUG_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
……
}
特別解釋:
_CRTDBG_LEAK_CHECK_DF | OFF | 打開:在程序退出時通過對?_CrtDumpMemoryLeaks?的調用執行自動泄露檢查,如果應用程序無法釋放它分配的所有內存,則生成錯誤報告。 關閉:不要在程序退出時自動執行泄露檢查。 還可以與任何堆頻率檢查宏組合。 |
盡量避免不必要得使用:_CrtDumpMemoryLeaks
?
6、deepseek的
關于 `_CrtDumpMemoryLeaks` 在 MFC 中的使用位置和避免誤報的問題,你的理解非常正確。`ExitInstance` 和析構函數確實是常見的誤報點,核心原因就在于 檢測時機過早。下面詳細解釋原因并提供解決方案:1. 為什么在 `ExitInstance` 或析構函數中使用 `_CrtDumpMemoryLeaks` 會產生誤報?MFC 框架/靜態對象清理滯后: `ExitInstance` 是 `CWinApp` 的成員函數,它在應用程序的主消息循環結束后、主窗口已銷毀時調用。析構函數(尤其是 `CWinApp` 的析構函數)也發生在這個階段。關鍵點在于:MFC 內部對象: MFC 框架自身可能還有一些內部對象(如文檔模板、線程局部存儲、內部緩存等)需要在 `ExitInstance` 之后或 `CWinApp` 析構之后才被完全清理。全局/靜態對象: 你的代碼或第三方庫中定義的全局對象 (`CMyGlobalObject globalObj;`) 或靜態對象 (`static CMyStaticObject staticObj;`) 的析構函數,它們的執行順序是由編譯器決定的,并且可能發生在 `CWinApp` 析構之后。
檢測時機過早: 在 `ExitInstance` 或 `CWinApp` 析構函數中調用 `_CrtDumpMemoryLeaks` 時,上述那些滯后清理的 MFC 內部對象和全局/靜態對象還沒有被銷毀。這些對象所占用的內存,在 `_CrtDumpMemoryLeaks` 看來就是“尚未釋放”的內存,從而被錯誤地報告為內存泄漏。總結: `ExitInstance` 和析構函數發生在應用程序生命周期的“中間”階段,此時并非所有內存使用者(尤其是全局/靜態對象)都已結束生命并釋放內存。在此處檢測,必然會將那些稍后才釋放的對象誤報為泄漏。2. 如何正確使用 `_CrtDumpMemoryLeaks` 避免誤報?解決方案的核心在于:確保檢測發生在所有用戶代碼(包括全局/靜態對象析構)和 MFC 框架清理工作都已完成之后。 以下是幾種推薦且有效的方法:方法 1: 利用 `_CrtSetDbgFlag` 設置自動報告 (最推薦)原理: 在 `InitInstance` 的最開始設置調試標志,告訴 C 運行時庫在程序完全退出時(即所有全局/靜態對象析構完成后)自動調用 `_CrtDumpMemoryLeaks`。
位置: `CMyApp::InitInstance()` 的開頭。
代碼:```cppBOOL CMyApp::InitInstance(){// 在 InitInstance 最開始設置內存泄漏檢測標志 (僅 Debug 模式)#ifdef _DEBUG_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);#endif// ... 其他初始化代碼 (注冊文檔模板、創建主窗口等) ...return TRUE;}```優點:時機最準確: 檢測發生在程序生命周期的最后(所有全局/靜態對象析構后),最大程度減少誤報。無需手動調用: 自動執行,方便可靠。標準做法: 這是 VC++ 調試內存泄漏的官方推薦方式。
缺點: 輸出在調試器輸出窗口(如 VS 的 Output 窗口),可能不如寫入文件直觀(但通常足夠)。方法 2: 在 `main`/`WinMain` 函數返回前調用 (適用于明確入口點)原理: 如果你的應用程序入口點是標準的 `WinMain` 或 `main` (MFC 通常隱藏了 `WinMain`),可以在入口函數的 `return` 語句之前調用 `_CrtDumpMemoryLeaks`。
位置: `WinMain` 或 `main` 函數結束前 (`return` 之前)。
代碼 (示例 `WinMain`, 通常由 MFC 提供):```cpp#include <afxwin.h> // 確保包含 MFC 頭文件#include <crtdbg.h>int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){// ... MFC 初始化 (通常隱含在 AfxWinMain 中) ...CWinApp* pApp = AfxGetApp();if (pApp != nullptr){pApp->InitApplication();pApp->InitInstance();pApp->Run();pApp->ExitInstance(); // MFC 框架調用}// 在 WinMain 返回前,所有全局/靜態對象應已析構#ifdef _DEBUG_CrtDumpMemoryLeaks(); // 在此處檢測#endifreturn 0;}```實際應用中,你通常需要修改 MFC 提供的 `WinMain` 實現(在 `appmodul.cpp` 或類似文件中)。
優點: 時機也相對較晚(在 `ExitInstance` 之后,所有全局/靜態對象析構后,程序真正結束前)。
缺點:MFC 應用程序的入口點通常被框架封裝 (`AfxWinMain`),直接修改它需要找到對應的源文件(如 `appmodul.cpp`)并可能影響項目結構。不如方法 1 方便通用。方法 3: 使用靜態對象析構函數 (高級技巧)原理: 定義一個全局的靜態對象。C++ 保證全局靜態對象在 `main`/`WinMain` 結束后、程序完全退出前析構。在這個靜態對象的析構函數中調用 `_CrtDumpMemoryLeaks`。
位置: 在一個全局靜態對象的析構函數中。
代碼:```cpp// 在你的某個 .cpp 文件中class LeakDetectorFinalizer {public:~LeakDetectorFinalizer() {#ifdef _DEBUG_CrtDumpMemoryLeaks(); // 在靜態對象析構時檢測#endif}};// 定義一個全局靜態實例static LeakDetectorFinalizer s_finalLeakChecker;```
優點: 理論上時機也很晚(在大多數其他全局/靜態對象析構之后)。
缺點:析構順序不確定性: C++ 標準只規定了同一編譯單元內靜態對象析構的逆初始化順序,不同編譯單元間的析構順序是未定義的。如果存在其他全局靜態對象依賴后釋放的資源,且它們在 `s_finalLeakChecker` 之后析構,那么這些對象仍會被誤報為泄漏。這種方法不如方法 1 可靠。略顯晦澀。總結與最佳實踐1. 絕對避免在 `ExitInstance` 和 `CWinApp` 析構函數中調用 `_CrtDumpMemoryLeaks`: 這是誤報的主要根源。
2. 首選方法: 在 `InitInstance` 開頭使用 `_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);`。這是 VC++/MFC 調試內存泄漏的標準、最可靠、誤報最少的方法。它確保了檢測發生在程序生命周期的最后。
3. 替代方法 (謹慎使用):修改 `WinMain`/`main` 在 `return` 前調用:時機正確,但修改 MFC 入口點文件稍顯麻煩。靜態對象析構函數調用:理論上可行,但受限于靜態對象析構順序的不確定性,不推薦作為首選。
4. 僅限 Debug 模式: 務必使用 `#ifdef _DEBUG` 和 `#endif` 將內存泄漏檢測代碼包裹起來,確保它們只在調試版本中編譯和執行。在 Release 版本中檢測不僅無效(因優化可能導致信息不準),還會增加不必要的開銷。
5. 理解輸出: 即使使用最佳方法,有時也會看到一些來自 MFC 內部或第三方庫的“報告”。學習識別這些常見的“偽泄漏”(通常分配號固定)或使用 `_CrtSetBreakAlloc` 配合分配號來精確定位真正的泄漏點。`_CrtMemCheckpoint` 和 `_CrtMemDifference` 可用于在程序關鍵點進行更精細的內存狀態快照比較。核心要點:延遲檢測! 讓 `_CrtDumpMemoryLeaks` 在程序真正結束、所有資源(尤其是全局/靜態對象)都釋放完畢之后再執行,是解決誤報問題的關鍵。`_CrtSetDbgFlag` 方法正是利用了 C 運行時庫自身在退出時的最后清理點來保證這一點。
7、斷點示例(實操)
錯誤太多就分模塊屏蔽
錯誤提示
Detected memory leaks!
Dumping objects ->
{9986} normal block at 0x00000248AB882D40, 1 bytes long.Data: < > CD
{9978} normal block at 0x00000248AB88F300, 16 bytes long.Data: < H > E0 D3 88 AB 48 02 00 00 00 00 00 00 00 00 00 00
代碼斷點
BOOL X::InitInstance()
{// 啟用自動內存泄漏檢測(調試模式)
#ifdef _DEBUG_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);_CrtSetBreakAlloc(9986);
#endif
……
}
代碼定位問題出處
(自定定義的單例把釋放的new屏蔽掉了導致的)
8、內存泄露實操方案(Hook)
8.1、在自定的.cpp文件加上
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
?8.2 使用hook
#ifdef _NewLeak
int __cdecl AllocHook(int allocType, void* userData, size_t size,int blockType, long requestNumber, const unsigned char* filename, int lineNumber) {char buffer[512];if (requestNumber > 0) // 屏蔽掉分配的{sprintf_s(buffer, sizeof(buffer),"分配序號: %ld, 大小: %zu, 文件: %s, 行號: %d\n",requestNumber, size, filename ? (const char*)filename : "未知", lineNumber);OutputDebugStringA(buffer);}return TRUE;
}
#endifBOOL X::InitInstance()
{// 啟用自動內存泄漏檢測(調試模式)
#ifdef _NewLeak_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);_CrtSetAllocHook(AllocHook);
#endif
……
}
8.3 可以在自己new的代碼打個斷點執行完畢即可看到? 輸出框 職中的顯示
9、內存泄露實操
分段屏蔽,逐步縮小內存泄露范圍
比如經過屏蔽測試
?m_capture.open(0); 會導致內存泄露