1. 项目概述:为什么我们需要VLD?
在Windows平台上用C++做开发,内存泄漏是个老生常谈但又避不开的痛点。不像Java、C#有垃圾回收器(GC)兜底,C++把内存的生杀大权完全交给了开发者。这份自由意味着责任,一个new忘了delete,一个malloc忘了free,日积月累,轻则程序内存占用像吹气球一样膨胀,性能下降;重则直接崩溃,在服务端场景可能就是一次线上事故。
我经历过最头疼的一次排查,是一个运行了数周的服务进程,内存缓慢增长,从最初的几百兆涨到了几个G。没有明确的崩溃点,只是响应越来越慢。当时手头工具有限,只能靠经验去猜,在几十万行代码里大海捞针,加了无数日志,花了将近一周才定位到一个第三方库回调函数里一个极其隐蔽的指针赋值错误。如果当时项目初期就集成了像Visual Leak Detector(VLD)这样的工具,可能只需要一次Debug运行,问题就一目了然了。
VLD的魅力就在于此:它把事后艰难的“破案”过程,变成了开发阶段自动化的“体检”。它是一款专为Visual C++设计的、开源免费的内存泄漏检测工具。其原理是在程序退出时,挂钩(Hook)内存分配和释放函数,对比整个生命周期内的分配和释放记录,最终生成一份详细的泄漏报告,直接告诉你哪一行代码分配的内存没有被释放。对于C++开发者,尤其是刚入门、对资源管理还不那么熟练的朋友来说,这简直就是“保姆级”的防错助手。当然,工具再好也只是辅助,真正的修养是掌握RAII(资源获取即初始化)、智能指针等现代C++理念,从源头避免泄漏。但在这之前,让VLD为你保驾护航,无疑是最高效、最稳妥的选择。
2. VLD的核心工作原理与优势解析
2.1 VLD是如何“看见”内存泄漏的?
很多新手可能会好奇,VLD又不是神仙,它怎么知道哪块内存漏了?其实它的原理并不神秘,核心在于“拦截”和“记账”。
在Windows的VC++运行时环境中,我们常用的new/delete、malloc/free等内存操作,其底层最终都会调用到像HeapAlloc、HeapFree这样的Win32 API。VLD在初始化时,会利用微软提供的Detours库或类似的钩子技术,去“劫持”(Hook)这些关键的内存分配和释放函数。
当你的程序启动,VLD初始化后,整个内存活动的流程就变成了这样:
- 你的代码调用
p = new MyClass()。 - 这个调用被VLD的钩子函数截获。
- VLD首先记录下这次分配:内存地址(0x00xxxxxx)、大小(sizeof(MyClass))、以及当时的调用堆栈(Call Stack)。这个堆栈信息至关重要,它记录了是从哪个函数、哪一行代码发起的这次分配。
- 然后,VLD再将分配请求传递给真正的内存分配函数(如
HeapAlloc),拿到内存地址后返回给你的程序。 - 当你的代码调用
delete p时,同样会被VLD截获。 - VLD在自己的“账本”里,查找这个内存地址对应的分配记录,然后将其标记为“已释放”,并从当前活跃分配列表中移除。
- 最后,再将释放请求传递给真正的释放函数。
程序正常结束时,VLD会做一个“期末盘点”:检查自己的“账本”。如果发现还有任何一条分配记录没有被标记为释放,那么这些就是“内存泄漏”。VLD会将这些泄漏记录的详细信息——包括内存地址、大小、以及当初分配时的完整调用堆栈——输出到调试器(如Visual Studio的输出窗口)或指定的文件中。
注意:VLD的这种钩子机制决定了它主要适用于Debug构建。因为在Release构建中,编译器会进行大量优化(如函数内联),导致调用堆栈信息不完整或失真,影响定位准确性。同时,钩子本身也会带来一定的性能开销,所以通常只在调试阶段启用。
2.2 对比其他工具,VLD的优势在哪?
Windows下内存检测工具不止VLD一个,比如商业软件Deleaker、Visual Studio自带的CRT调试库等。那为什么我尤其推荐VLD?
1. 无缝集成与近乎零成本使用:VLD的使用简单到令人发指。理论上,你只需要在项目中包含一个头文件#include <vld.h>,配置好库路径,它就开始工作了。不需要修改你的业务代码,不需要特殊的编译指令(CRT调试库通常需要定义_CRTDBG_MAP_ALLOC等宏),对项目入侵性极小。这种“即插即用”的特性,使得将其集成到现有大型项目中变得非常容易。
2. 精准的堆栈信息与源码定位:这是VLD的杀手锏。它报告的泄漏点不是模糊的地址,而是可以直接映射到源代码文件、行号、函数名的调用堆栈。你看到的就是“main.cpp第42行,在MyFunction()中分配了100字节”。这比只给出一个地址或者模块名要直观太多,大大缩短了排查时间。这个功能的实现依赖于PDB(程序数据库)文件,所以确保你的Debug构建生成了正确的PDB文件。
3. 开源、免费且持续维护:VLD是开源项目(早期版本在CodePlex,现在有GitHub分支),这意味着你可以免费用于商业项目。虽然官方原版(v2.5.1)停止在VS2015,但社区活跃,已经有了支持更高版本VS(如2019,2022)的分支(如v2.7.0)。免费和开源避免了采购审批、许可协议等麻烦,特别适合个人开发者、创业团队和学生。
4. 对C和C++的全面支持:无论是传统的C风格malloc/free,还是C++的new/delete、new[]/delete[],VLD都能很好地跟踪。它底层拦截的是更基础的内存分配器,因此兼容性很强。
5. 可定制的输出与报告:VLD允许你通过配置文件(vld.ini)或API来定制行为。比如,你可以将报告重定向到文件而不是调试器,可以设置内存泄漏的阈值(小于多少字节的泄漏不报告),甚至可以排除某些模块或特定分配函数产生的报告(这在处理某些已知存在“伪泄漏”的第三方库时非常有用)。
相比之下,VS自带的CRT调试库功能较弱,报告信息不够直观;而像Deleaker这样的商业工具虽然功能强大,但需要付费。对于大多数日常开发和调试场景,VLD在功能、易用性和成本之间取得了绝佳的平衡。
3. 手把手实战:在Visual Studio 2022中集成VLD
网上很多教程还停留在VLD的VS插件时代,但那个插件已经很久不更新了。现在更推荐直接以库的方式集成,这种方式更通用、更稳定,不受特定VS版本限制。下面我以社区维护的VLD 2.7.0版本和VS2022为例,演示完整的集成步骤。
3.1 获取与部署VLD
第一步:下载VLD 2.7.0官方原版(2.5.1)的网站更新停滞。我们可以使用GitHub上社区维护的版本。你可以直接去GitHub搜索“Visual Leak Detector”找到相关仓库,或者从可靠的镜像站点下载预编译的vld-2.7.0-setup.exe安装包。
第二步:安装与目录整理运行安装程序,建议安装到一个没有中文和空格的路径,例如D:\DevTools\VLD\v2.7.0。安装完成后,查看安装目录,核心是三个文件夹:
include: 包含头文件vld.h和vld_def.h。lib: 包含导入库文件vld.lib,通常下面有Win32和x64子目录,对应不同平台。为了保持命名一致性(与VS平台配置$(Platform)匹配),建议将Win32重命名为x86。bin: 包含运行时所需的DLL文件(vld_x86.dll或vld_x64.dll)、对应的PDB文件以及依赖的dbghelp.dll。
第三步:设置环境变量(可选但推荐)为了在多个项目中方便引用,可以设置一个系统或用户环境变量VLD_ROOT,值为你的VLD安装路径,如D:\DevTools\VLD\v2.7.0。这样在项目配置中就可以使用$(VLD_ROOT)来引用,避免硬编码绝对路径。
3.2 项目配置详解
假设我们有一个名为MyApp的Visual Studio 2022控制台项目,我们需要为它的Debug配置集成VLD。
1. 包含目录配置:右键项目 -> 属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录。 添加:$(VLD_ROOT)\include。确保配置为“所有配置”和“所有平台”,因为头文件本身无害,但为了清晰,可以只给Debug配。
2. 库目录配置:右键项目 -> 属性 -> 配置属性 -> 链接器 -> 常规 -> 附加库目录。 这里必须区分配置和平台。选择配置为“Debug”,平台根据需要选择“x64”或“Win32”。 添加:$(VLD_ROOT)\lib\$(Platform)。$(Platform)宏在x64配置下值为x64,在Win32配置下值为Win32(或你重命名后的x86)。这样就能自动找到对应平台的vld.lib。
3. 附加依赖项配置:右键项目 -> 属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项。 同样,选择“Debug”配置。添加:vld.lib。
4. 后期生成事件(关键步骤):VLD需要它的DLL文件在程序运行时能被找到。最简单的方法是将DLL复制到你的程序输出目录。 右键项目 -> 属性 -> 配置属性 -> 生成事件 -> 后期生成事件 -> 命令行。 选择“Debug”配置和对应平台,添加命令:
copy "$(VLD_ROOT)\bin\$(Platform)\vld*.dll" "$(TargetDir)" copy "$(VLD_ROOT)\bin\$(Platform)\dbghelp.dll" "$(TargetDir)" 2>nul || echo dbghelp.dll not found or already exists.第一行复制VLD主DLL,第二行复制其依赖的dbghelp.dll。2>nul是为了抑制文件不存在时的错误提示,因为高版本Windows可能自带该DLL。
3.3 代码集成与基础使用
配置好项目后,在代码中使用VLD就非常简单了。通常,我们在主程序文件(如main.cpp或WinMain.cpp)的顶部包含VLD头文件。
// main.cpp #include <iostream> // 仅在Windows Debug构建下启用VLD #if defined(_WIN32) && defined(_DEBUG) #include <vld.h> #endif void LeakyFunction() { int* p = new int(42); // 这里分配了内存,但没有释放! // 忘记 delete p; } int main() { std::cout << "VLD Memory Leak Detection Demo" << std::endl; LeakyFunction(); std::cout << "Function called, check output for leaks." << std::endl; return 0; }编译并运行这个程序的Debug版本(确保是x64或x86的Debug配置)。程序运行结束后,不要急着关闭控制台窗口,查看Visual Studio的“输出”窗口(通常视图 -> 输出,或按Ctrl+Alt+O),选择显示输出来源为“调试”,你应该能看到类似下面的报告:
Visual Leak Detector read settings from: D:\DevTools\VLD\v2.7.0\bin\x64\vld.ini Visual Leak Detector Version 2.7.0 installed. WARNING: Visual Leak Detector detected memory leaks! ---------- Block 1 at 0x000001F0B5B72B70: 4 bytes ---------- Leak Hash: 0x2A3B4C5D, Count: 1 Call Stack (TID 1234): ucrtbased.dll!malloc() MyApp.exe!operator new() (d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\heap\new_array.cpp:15) MyApp.exe!LeakyFunction() (d:\projects\myapp\main.cpp:8) MyApp.exe!main() (d:\projects\myapp\main.cpp:15) MyApp.exe!invoke_main() ... Data: 2A 00 00 00 *... // 内存中的数据,0x0000002A即十进制42报告清晰地指出:在main.cpp第8行,LeakyFunction()函数中,通过operator new分配了4个字节(一个int)的内存发生了泄漏,并且显示了泄漏内存中存储的数据。根据这个信息,你就能快速定位到问题源头。
实操心得:有时你可能在输出窗口看不到VLD的报告。请确保:1. 项目确实是Debug配置。2. 程序是正常退出(例如控制台程序运行到
return),而不是被强行终止(在IDE中按停止按钮)。强行终止会跳过VLD的清理和报告流程。3. 检查“输出”窗口的筛选器,确保“调试”信息没有被过滤掉。
4. 进阶技巧与疑难问题排查
掌握了基本用法,我们来看看如何应对更复杂的场景,以及解决那些可能让你头疼的常见问题。
4.1 处理第三方库与“伪泄漏”
有时候,VLD会报告一些来自系统库或第三方库的“泄漏”。这些可能并非真正的泄漏,而是因为这些库在内部维护了全局缓存,直到进程结束才释放,或者它们使用了自定义的内存分配器,VLD无法准确跟踪。
方法一:使用VLD配置文件排除在VLD的bin目录下(或者复制到你的程序运行目录),有一个vld.ini文件。你可以通过修改它来过滤这些报告。
ReportTo: 设置报告输出位置(debugger,file,both)。ReportFile: 如果输出到文件,指定文件名。SkipLibs: 这是一个强大的选项。你可以列出需要跳过的模块名(DLL或EXE)。例如,你发现SomeThirdParty.dll总是报告泄漏,但确认其是安全的,可以添加SkipLibs = SomeThirdParty.dll。多个模块用逗号分隔。ForceIncludeModules: 强制包含某些模块进行检测,即使它们通常被排除。MaxDataDump: 控制泄漏内存数据转储的最大字节数,默认是256。
方法二:在代码中动态排除VLD也提供了API,可以在运行时控制。你需要包含vld.h后,调用VLDEnable,VLDDisable,VLDReportLeaks等函数。例如,在调用一个已知会触发“伪泄漏”报告的第三方库函数前后,可以暂时禁用VLD:
#include <vld.h> extern void SomeNoisyLibraryCall(); void MyFunction() { VLDDisable(); // 开始调用前禁用检测 SomeNoisyLibraryCall(); VLDEnable(); // 调用结束后重新启用 // ... 你自己的代码,继续受VLD监控 }方法三:忽略特定内存块(谨慎使用)对于确认为非泄漏的、但又无法通过上述方法排除的特定分配,VLD提供了VLDMarkAllLeaksAsReported()和VLDMarkThreadLeaksAsReported()函数。调用它们会将当前已检测到的所有泄漏标记为“已报告”,从而在最终报告中被忽略。这非常危险,因为它会掩盖真实的泄漏,所以除非你百分百确定,否则不要使用。
4.2 多线程环境下的注意事项
VLD是线程安全的,可以在多线程程序中使用。但是,在多线程场景下,报告的输出可能会交错,因为每个线程在退出时都可能触发泄漏检查。为了获得更清晰的报告,可以考虑:
- 在主线程(或主控制逻辑)中集中管理VLD:确保VLD的初始化发生在所有工作线程启动之前,而最终报告发生在所有工作线程安全结束之后。
- 关注线程局部存储(TLS)泄漏:VLD能检测到通过标准
new/malloc分配的泄漏,但如果泄漏发生在线程局部存储中,且该线程在进程结束前没有结束,这块内存可能直到进程结束才被视为“泄漏”。理解这一点有助于分析报告。 - 使用
VLDReportLeaks()主动报告:你可以在程序的关键节点(如一个请求处理完毕,或一个测试用例结束时)调用VLDReportLeaks(),输出从上次报告到当前时刻的泄漏情况。这对于定位周期性任务或特定操作导致的内存增长很有帮助。
4.3 常见问题与解决方案实录
问题1:编译时链接错误 LNK2019 或 LNK1104
- 错误示例:
error LNK2019: 无法解析的外部符号 __imp_VLDEnable或error LNK1104: 无法打开文件“vld.lib” - 排查:
- 检查配置:确认项目属性中的“附加库目录”和“附加依赖项”是否严格按照Debug配置和正确的平台(x64/Win32)设置。
- 检查路径:确认
$(VLD_ROOT)环境变量是否正确设置,或者使用的绝对路径是否有效。可以打开“开发者命令提示符”,输入echo %VLD_ROOT%验证。 - 检查文件:去
$(VLD_ROOT)\lib\$(Platform)目录下确认vld.lib文件是否存在。 - 清理与重建:有时VS的缓存会导致问题,尝试“清理解决方案”,然后重新生成。
问题2:运行时错误:找不到vld_x64.dll(或vld_x86.dll)
- 错误示例:应用程序无法启动,因为缺少
vld_x64.dll。 - 排查:
- 检查后期生成事件:确认后期生成事件的命令是否正确执行。可以尝试在命令后加
pause,查看复制过程是否出错。 - 检查目标目录:去你的项目输出目录(
$(TargetDir),通常是Debug或x64\Debug)下查看,是否存在vld_x64.dll和dbghelp.dll。 - 系统路径:也可以将VLD的
bin\$(Platform)目录添加到系统的PATH环境变量中,但这通常不是最佳实践。
- 检查后期生成事件:确认后期生成事件的命令是否正确执行。可以尝试在命令后加
问题3:VLD没有输出任何报告
- 排查:
- 构建配置:百分之百确认你运行的是Debug版本。Release版本下,
_DEBUG宏未定义,#include <vld.h>的代码块被跳过。 - 输出窗口:确保你在VS中查看的是“输出”窗口,并且“显示输出来源”选择了“调试”。有时报告可能被大量其他输出淹没,可以尝试搜索“Visual Leak Detector”或“memory leaks”。
- 程序退出方式:程序必须是正常退出的。如果是你在调试器中按了“停止调试”按钮,或者程序因未处理的异常而崩溃,VLD可能没有机会生成报告。
- 其他调试器:如果你在使用其他调试器(如WinDbg),VLD默认输出可能不显示。需要配置
vld.ini中的ReportTo选项,将报告输出到文件(ReportTo = file)。
- 构建配置:百分之百确认你运行的是Debug版本。Release版本下,
问题4:报告中的调用堆栈没有符号(函数名和行号)
- 现象:报告中只有地址,如
0x7FFA12345678,没有文件名和行号。 - 原因:PDB(符号)文件未加载或未生成。
- 解决:
- 确保项目属性 -> C/C++ -> 常规 -> 调试信息格式设置为“程序数据库(/Zi)”。
- 确保链接器 -> 调试 -> 生成调试信息设置为“是(/DEBUG)”。
- 对于你自己的项目,这通常是默认的。如果堆栈显示的是系统DLL(如
ntdll.dll),你需要配置符号服务器来获取微软的系统符号。在VS中,工具 -> 选项 -> 调试 -> 符号,勾选“Microsoft符号服务器”。首次加载可能需要一些时间。
5. 超越基础:将VLD融入开发与测试流程
VLD不仅仅是一个调试时临时打开的工具,将它融入团队的开发流程,能极大提升代码质量和排查效率。
5.1 在单元测试中集成VLD
对于C++项目,为关键模块编写单元测试是保证质量的重要手段。我们可以在单元测试框架中集成VLD,让每次测试运行都自动进行内存泄漏检查。
以Google Test为例,你可以创建一个测试夹具(Test Fixture),在SetUp和TearDown中管理VLD的状态:
// vld_gtest_integration.h #pragma once #include <gtest/gtest.h> #if defined(_WIN32) && defined(_DEBUG) #include <vld.h> #endif class VldTestFixture : public ::testing::Test { protected: void SetUp() override { #if defined(_WIN32) && defined(_DEBUG) // 每次测试开始前,清除之前的泄漏计数,以便只报告本次测试中产生的泄漏 VLDMarkAllLeaksAsReported(); VLDEnable(); #endif } void TearDown() override { #if defined(_WIN32) && defined(_DEBUG) // 每次测试结束后,立即报告泄漏。如果测试通过但存在泄漏,则此断言会失败。 EXPECT_EQ(VLDReportLeaks(), 0) << "Memory leaks detected in test!"; VLDDisable(); #endif } };然后,你的测试用例可以继承自这个VldTestFixture:
TEST_F(VldTestFixture, MyMemorySafeFunctionTest) { MyClass* obj = new MyClass(); delete obj; // 正确释放,测试通过 } TEST_F(VldTestFixture, MyLeakyFunctionTest) { int* p = new int[100]; // 忘记 delete[] p; 这个测试会在TearDown中失败,并报告泄漏详情 }这样,每次运行单元测试,就相当于对被测代码进行了一次内存泄漏扫描。CI/CD流水线可以自动捕获这些失败,防止有泄漏的代码被合并到主分支。
5.2 配置项目模板与共享属性表
如果你在团队中推广VLD,为每个新项目手动配置一遍会很麻烦。可以利用Visual Studio的“属性表”功能。
- 创建一个新的属性表(视图 -> 属性管理器 -> 右键你的项目 -> 添加新项目属性表),命名为
VLD_Debug.props。 - 在这个属性表中,按照第3.2节的方法,配置好Debug模式下的包含目录、库目录、附加依赖项和后期生成事件。
- 将这个
.props文件保存到团队共享的目录或源码库中。 - 以后任何新项目,只需要在属性管理器中“添加现有属性表”,选择这个
VLD_Debug.props,就一键完成了所有VLD的Debug配置。
5.3 解读复杂泄漏报告与性能考量
当面对一个大型项目产生的复杂泄漏报告时,可以遵循以下策略:
- 按大小排序:优先解决那些泄漏字节数大的块。一个泄漏了1MB的块比1000个泄漏了1字节的块对程序的影响通常更直接。
- 寻找模式:如果报告中有大量相同大小、相同调用堆栈的泄漏,很可能是一个在循环或高频函数中重复发生的泄漏。
- 关注自己的代码:首先过滤掉第三方库的泄漏(使用
SkipLibs),集中精力解决自己代码触发的部分。 - 理解智能指针与VLD:现代C++中广泛使用
std::unique_ptr和std::shared_ptr。VLD能很好地处理它们。如果智能指针管理的内存最终没有被释放(例如,由于循环引用导致shared_ptr无法析构),VLD同样会报告泄漏,并且堆栈会指向创建该智能指针(即调用new)的地方。
关于性能:VLD在Debug构建中会带来明显的性能开销和内存占用增长,因为它要记录每一次分配和释放。这是完全正常的,也是预期的。VLD就是为了调试而生的。永远不要在Release构建中启用VLD进行性能测试或发布。它的开销使得它不适合用于长期运行的生产环境监控。对于生产环境的内存问题,需要使用其他工具,如Windows Performance Analyzer (WPA)、Valgrind(Linux跨平台)或专门的Profiler。
6. 从检测到根治:C++内存管理的核心法则
VLD帮我们找到了泄漏点,但修复泄漏、并最终写出内存安全的代码,还需要我们掌握正确的理念和方法。这里分享几条我实践中认为最重要的原则。
1. RAII(资源获取即初始化):这是C++资源管理的基石。核心思想:将资源的生命周期与对象的生命周期绑定。在构造函数中获取资源(内存、文件句柄、锁等),在析构函数中释放资源。这样,只要对象在栈上或能被正确管理,资源就一定会被释放。
// 传统易错方式 void risky() { FILE* f = fopen("data.txt", "r"); // ... 如果这里return或抛异常,文件句柄泄漏! fclose(f); } // RAII方式 (C++11后可用std::unique_ptr配合自定义删除器,或直接用ifstream) class FileHandle { FILE* fp; public: explicit FileHandle(const char* filename, const char* mode) : fp(fopen(filename, mode)) { if (!fp) throw std::runtime_error("Failed to open file"); } ~FileHandle() { if (fp) fclose(fp); } // 禁用拷贝 FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; // 允许移动(可选) FileHandle(FileHandle&& other) noexcept : fp(other.fp) { other.fp = nullptr; } // 使用接口 operator FILE*() const { return fp; } }; void safe() { FileHandle f("data.txt", "r"); // 资源在构造时获取 // ... 使用 f // 无论函数如何退出(正常return、异常),f的析构函数都会被调用,文件被关闭。 }2. 优先使用智能指针,而非裸指针。std::unique_ptr用于独占所有权,std::shared_ptr用于共享所有权,std::weak_ptr用于打破shared_ptr的循环引用。99%的情况下,你都不应该再看到new和delete成对出现在业务逻辑代码中。
// Bad MyClass* obj = new MyClass(); // ... 一大堆可能提前返回或抛异常的代码 delete obj; // Good auto obj = std::make_unique<MyClass>(); // ... 安心了,内存自动管理。无需手动delete。3. 遵循“谁分配,谁释放”的约定,并在模块/类层面明确所有权。如果一个函数返回了一个动态分配对象的指针,必须在文档中明确指出调用者是否获得了该对象的所有权(即是否需要负责delete)。更好的做法是直接返回智能指针,所有权语义一目了然。
4. 使用标准库容器(如std::vector,std::string)替代手动数组管理。std::vector和std::string内部管理内存,你几乎不用担心它们的泄漏问题(除非你用了reserve然后又用裸指针乱搞)。
5. 对代码进行分层和模块化测试。VLD在集成测试或整体运行时很好用,但对于庞大的代码库,泄漏可能藏在深处。结合单元测试(如5.1节所述)对每个模块进行独立的内存泄漏测试,能更早发现问题。在修复一个泄漏后,增加一个对应的单元测试来防止回归。
工具如VLD是我们的“安全网”,但最坚固的防线始终是我们对语言特性的深刻理解和对良好编程习惯的坚持。每次VLD报告泄漏时,不要仅仅把它当作一个需要修复的Bug,更把它当作一次反思代码设计、巩固内存管理知识的机会。久而久之,你写出内存安全代码的肌肉记忆就会形成,VLD的报告也会变得越来越干净。