Visual Studio下的內(nèi)存安全檢測:CRT 內(nèi)存泄漏 & AddressSanitizer
前言
筆者之前有一篇博客專門介紹了GNU工具鏈下的gcc如何啟動AddressSanitizer來檢查內(nèi)存安全檢查
但是我發(fā)現(xiàn),很多朋友包括我自己之后的工作是在Windows上做的,為此就有必要單獨(dú)拉出來談一談Visual Studio(或者是使用MSVC編譯器的命令行)是如何檢查我們的內(nèi)存安全的。這里我們列出來三個渠道。筆者比較建議第二個來檢查內(nèi)存泄露,第三個檢查檢測越界和 Use-after-free等經(jīng)典內(nèi)存誤用問題。
使用 CRT Debug Heap 檢測內(nèi)存泄漏;
使用 Diagnostic Tools → Memory Usage 分析堆快照和分配調(diào)用棧;
了解 AddressSanitizer 檢測越界和 Use-after-free
內(nèi)存泄漏檢測(CRT Debug Heap)
Microsoft C Runtime自己就有Debug插樁機(jī)制來檢查我們的內(nèi)存安全。如果您不太相信,您可以這樣做:
#define _CRTDBG_MAP_ALLOC // 定義在待檢查文件的開頭
#include <crtdbg.h> // 這個頭文件要被包含進(jìn)來,從而使用代碼層次上的插樁
#include <cstdlib>
#include <iostream>
#include <cstring>
// CRT Debug要在運(yùn)行期啟動,這不奇怪,都是C RunTime提供的機(jī)制了,自然運(yùn)行期執(zhí)行代碼對不對?
void enable_memory_debug() {
int dbgFlags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
dbgFlags |= _CRTDBG_LEAK_CHECK_DF;
dbgFlags |= _CRTDBG_ALLOC_MEM_DF;
_CrtSetDbgFlag(dbgFlags);
// 強(qiáng)制 CRT 報(bào)告輸出到 stdout, 下面這幾步至少在VS2026是必須要做的
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);
}
int main() {
enable_memory_debug();
char* p = new char[10];
strcpy_s(p, 10, "Hello!");
std::cout << "Expected to trigger the overflow check!";
}
很好,到這里了,程序一結(jié)束,我們的程序尾巴就會打印內(nèi)從,如下所示
Detected memory leaks!
Dumping objects ->
{195} normal block at 0x00000246134FADC0, 10 bytes long.
Data: <Hello! > 48 65 6C 6C 6F 21 00 CD CD CD
Object dump complete.
這就對了,但是您有沒有發(fā)現(xiàn),他只是說明了咱們的內(nèi)存泄露是如何的,但是完全沒說在哪里泄露了。這是沒有意義的。所以,我們需要費(fèi)勁的寫下這段代碼
#ifdef _DEBUG
#define DEBUG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#define new DEBUG_NEW
#endif
這段代碼的意思是——將new重新定義為——插樁了文件所在地址和行數(shù)的new,然后定義回去。
main.cpp(36) : {195} normal block at 0x00000246134FADC0, 10 bytes long.
現(xiàn)在我們的信息就多出來了!
值得一提的是,犯不著等到程序結(jié)束讓程序自動觸發(fā)打印檢查,只需要我們調(diào)用_CrtDumpMemoryLeaks();
這個函數(shù)就會打印我們的泄露情況。
在特定分配斷點(diǎn)
但是好像還是不夠方便。有時候重定向new看起來怪怪的,我們改變了程序的分配內(nèi)存行為,有沒有比較低侵入的辦法呢?有。
您有注意到{195} normal block at 0x00000246134FADC0, 10 bytes long.的195沒?注意到的話,這就是我們內(nèi)存分配塊的分配序號。
這個時候,我們可以在代碼的開頭(需要注意的是,是在enable_debug()操作之后做,以及最好是任何new之前做。)寫下F5 調(diào)試,程序會在該次分配斷下,這個時候,我們就檢查調(diào)用堆棧,看看到底是哪一行的分配我們忘記釋放了。
Diagnostic Tools:堆快照分析
您會發(fā)現(xiàn)(筆者實(shí)際上做的時候就有感覺了),方式一比較原始,而且我們要做的處理非常繁雜,甚至還需要專門準(zhǔn)備開啟打印的內(nèi)容,其實(shí)適合的是自己代碼就有意檢查的模塊部分,但是我相信大部分情況下大家都是在救急。
期望趕快找到是哪里程序的內(nèi)存分配行為錯誤了。這個時候Diagnostic Tools就顯得十分的重要的了。
請注意,建議先打開斷點(diǎn),隨便在main的起頭把斷點(diǎn)上了,然后Debug → Windows → Show Diagnostic Tools。
注意,內(nèi)存診斷要開啟,筆者第一次做沒有開啟就發(fā)現(xiàn)不允許拍內(nèi)存快照,之后,我們需要做的是:
這是筆者的一個截圖,您能看到右下角的位置上,本來我們預(yù)期是內(nèi)存全部釋放的地方上卻上升了0.06KB(約10B,恰好就是我們泄露的點(diǎn))

當(dāng)然,點(diǎn)擊對象類型的項(xiàng)目,你就會自動跳轉(zhuǎn)到到底是誰干的分配了,這里不再贅述。
如果我們添加了delete[] p
,那就是用完后內(nèi)存就被歸還了。再拍兩次快照,對比差異接近 0,泄漏被修復(fù)。

3. AddressSanitizer(ASan)
如果對AddressSanitizer還不知道是啥的,筆者建議您去我開頭提到的博客看看:
AddressSanitizer 實(shí)踐:幾個常見的錯誤
Visual Studio下開啟并不困難,只需要勾選:
3.1 越界示例
char* p = new char[8];
for (int i = 0; i < 12; ++i) p[i] = 'A'; // OOB
delete[] p;
3.2 Use-after-free 示例
char* p = new char[16];
strcpy_s(p, 16, "hello asan");
delete[] p;
std::cout << p[0] << "\n"; // UAF
運(yùn)行后,ASan 會輸出:
AddressSanitizer: heap-buffer-overflow
AddressSanitizer: heap-use-after-free
同時顯示訪問地址、偏移、分配棧和釋放棧。
4. 小結(jié)與建議
組合使用這三種工具,你可以在 Windows 下 高效發(fā)現(xiàn)和修復(fù) C++ 內(nèi)存安全問題
參考文章:原文鏈接?
該文章在 2025/10/21 15:08:21 編輯過