Visual Studio下的内存安全检测:CRT 内存泄漏 & AddressSanitizer
前言
笔者之前有一篇博客专门介绍了GNU工具链下的gcc如何启动AddressSanitizer来检查内存安全检查
但是我发现,很多朋友包括我自己之后的工作是在Windows上做的,为此就有必要单独拉出来谈一谈Visual Studio(或者是使用MSVC编译器的命令行)是如何检查我们的内存安全的。这里我们列出来三个渠道。笔者比较建议第二个来检查内存泄露,第三个检查检测越界和 Use-after-free等经典内存误用问题。
使用 CRT Debug Heap 检测内存泄漏;
使用 Diagnostic Tools → Memory Usage 分析堆快照和分配调用栈;
了解 AddressSanitizer 检测越界和 Use-after-free
内存泄漏检测(CRT Debug Heap)
Microsoft C Runtime自己就有Debug插桩机制来检查我们的内存安全。如果您不太相信,您可以这样做:
#define _CRTDBG_MAP_ALLOC // 定义在待检查文件的开头
#include <crtdbg.h> // 这个头文件要被包含进来,从而使用代码层次上的插桩
#include <cstdlib>
#include <iostream>
#include <cstring>
// CRT Debug要在运行期启动,这不奇怪,都是C RunTime提供的机制了,自然运行期执行代码对不对?
void enable_memory_debug() {
int dbgFlags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
dbgFlags |= _CRTDBG_LEAK_CHECK_DF;
dbgFlags |= _CRTDBG_ALLOC_MEM_DF;
_CrtSetDbgFlag(dbgFlags);
// 强制 CRT 报告输出到 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!";
}
很好,到这里了,程序一结束,我们的程序尾巴就会打印内从,如下所示
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.
这就对了,但是您有没有发现,他只是说明了咱们的内存泄露是如何的,但是完全没说在哪里泄露了。这是没有意义的。所以,我们需要费劲的写下这段代码
#ifdef _DEBUG
#define DEBUG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#define new DEBUG_NEW
#endif
这段代码的意思是——将new重新定义为——插桩了文件所在地址和行数的new,然后定义回去。
main.cpp(36) : {195} normal block at 0x00000246134FADC0, 10 bytes long.
现在我们的信息就多出来了!
值得一提的是,犯不着等到程序结束让程序自动触发打印检查,只需要我们调用_CrtDumpMemoryLeaks();
这个函数就会打印我们的泄露情况。
在特定分配断点
但是好像还是不够方便。有时候重定向new看起来怪怪的,我们改变了程序的分配内存行为,有没有比较低侵入的办法呢?有。
您有注意到{195} normal block at 0x00000246134FADC0, 10 bytes long.的195没?注意到的话,这就是我们内存分配块的分配序号。
这个时候,我们可以在代码的开头(需要注意的是,是在enable_debug()操作之后做,以及最好是任何new之前做。)写下F5 调试,程序会在该次分配断下,这个时候,我们就检查调用堆栈,看看到底是哪一行的分配我们忘记释放了。
Diagnostic Tools:堆快照分析
您会发现(笔者实际上做的时候就有感觉了),方式一比较原始,而且我们要做的处理非常繁杂,甚至还需要专门准备开启打印的内容,其实适合的是自己代码就有意检查的模块部分,但是我相信大部分情况下大家都是在救急。
期望赶快找到是哪里程序的内存分配行为错误了。这个时候Diagnostic Tools就显得十分的重要的了。
请注意,建议先打开断点,随便在main的起头把断点上了,然后Debug → Windows → Show Diagnostic Tools。
注意,内存诊断要开启,笔者第一次做没有开启就发现不允许拍内存快照,之后,我们需要做的是:
这是笔者的一个截图,您能看到右下角的位置上,本来我们预期是内存全部释放的地方上却上升了0.06KB(约10B,恰好就是我们泄露的点)

当然,点击对象类型的项目,你就会自动跳转到到底是谁干的分配了,这里不再赘述。
如果我们添加了delete[] p
,那就是用完后内存就被归还了。再拍两次快照,对比差异接近 0,泄漏被修复。

3. AddressSanitizer(ASan)
如果对AddressSanitizer还不知道是啥的,笔者建议您去我开头提到的博客看看:
AddressSanitizer 实践:几个常见的错误
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
运行后,ASan 会输出:
AddressSanitizer: heap-buffer-overflow
AddressSanitizer: heap-use-after-free
同时显示访问地址、偏移、分配栈和释放栈。
4. 小结与建议
组合使用这三种工具,你可以在 Windows 下 高效发现和修复 C++ 内存安全问题
参考文章:原文链接
该文章在 2025/10/21 15:08:21 编辑过