Visual Studio下的内存安全检测:CRT 内存泄漏 AddressSanitizer
Visual Studio下的内存安全检测:CRT 内存泄漏 & AddressSanitizer
前言
笔者之前有一篇博客专门介绍了GNU工具链下的gcc如何启动AddressSanitizer来检查内存安全检查
AddressSanitizer 实践:几个常见的错误-CSDN博客
但是我发现,很多朋友包括我自己之后的工作是在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之前做。)写下
_CrtSetBreakAlloc(195);
F5 调试,程序会在该次分配断下,这个时候,我们就检查调用堆栈,看看到底是哪一行的分配我们忘记释放了。
Diagnostic Tools:堆快照分析
您会发现(笔者实际上做的时候就有感觉了),方式一比较原始,而且我们要做的处理非常繁杂,甚至还需要专门准备开启打印的内容,其实适合的是自己代码就有意检查的模块部分,但是我相信大部分情况下大家都是在救急。期望赶快找到是哪里程序的内存分配行为错误了。这个时候Diagnostic Tools就显得十分的重要的了。
请注意,建议先打开断点,随便在main的起头把断点上了,然后Debug → Windows → Show Diagnostic Tools
,注意,内存诊断要开启,笔者第一次做没有开启就发现不允许拍内存快照,之后,我们需要做的是
- 运行程序到想分析的点,点击 Take snapshot(Snapshot #1);
- 继续运行,到你认为程序一个功能职责结束的地方拍 Snapshot #2;
- 查看 Diff:增长对象表示潜在泄漏。
这是笔者的一个截图,您能看到右下角的位置上,本来我们预期是内存全部释放的地方上却上升了0.06KB(约10B,恰好就是我们泄露的点)
当然,点击对象类型的项目,你就会自动跳转到到底是谁干的分配了,这里不再赘述。
如果我们添加了delete[] p
,那就是用完后内存就被归还了。再拍两次快照,对比差异接近 0,泄漏被修复。
3. AddressSanitizer(ASan)
如果对AddressSanitizer还不知道是啥的,笔者建议您去我开头提到的博客看看
AddressSanitizer 实践:几个常见的错误-CSDN博客
Visual Studio下开启并不困难,只需要勾选:
- Project → Properties → C/C++ → General → Enable Address Sanitizer = Yes (/fsanitize=address)(Tips:中文的话是启用地址擦除系统,找了好久)
- Debug x64 模式,Clean rebuild 项目。
3.2 越界示例
char* p = new char[8];
for (int i = 0; i < 12; ++i) p[i] = 'A'; // OOB
delete[] p;
3.3 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. 小结与建议
- CRT Debug Heap:快速定位泄漏,配合
DEBUG_NEW
和_CrtSetBreakAlloc
,适合单次或短小程序分析。 - Diagnostic Tools:适合交互式堆快照对比,可在调试时精确定位泄漏对象和调用栈。
- AddressSanitizer:针对越界和 Use-after-free,非常精确,适合调试复杂堆访问错误。
组合使用这三种工具,你可以在 Windows 下 高效发现和修复 C++ 内存安全问题