C++中的内存泄漏检测,使用/不使用Visual Leak Detector

8
我想在Windows中检测我的C++程序的内存泄漏。 我阅读了MSDN上有关内存泄漏检测的文档,并开始使用Visual Leak Detector。
我对泄漏报告有疑问。 我期望看到文件名和行号,但是我总是看到下面的文本。 它包含了泄漏描述的所有组件(块类型、内存地址、数据等),除了文件名和行号。
如果这是一个真正的泄漏? 如果是,你知道为什么没有报告文件/行吗? 同时我也在查看此网址
谢谢
检测到内存泄漏! 正在转储对象 -> {4723} 位于0x04AFB5B8的普通块,长度为8字节。 数据:2C 3F 00 00 28 3F 00 00 {1476} 位于0x04AC3B58的普通块,长度为12字节。 数据:00 CD CD CD EB 01 75 4C CA 3D 0B 00 对象转储完成。

http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml - DumbCoder
这个话题有些偏离主题,但我之前被建议使用智能指针来避免手动内存管理和内存泄漏。 - Ahmed
你应该使用Deleaker。它一定会帮助你的。 - John Smith
7个回答

7
我研究了很多不同的内存泄漏跟踪方法。它们都有各自的优点和缺点。
要理解它们的优缺点,我们必须了解不同的机制和要求:
1. 如何拦截new、delete、malloc和free?一些工具使用#define重新定义new、delete、malloc和free,但这依赖于包含文件的正确顺序,并且如果一个类包含例如一个名为free的方法(如Qt中的情况),可能会出现问题。预处理器也将重新定义此方法,这可能导致编译错误或未解析的外部符号。 另一种方法是覆盖全局new和delete运算符。这是一个更清晰的解决方案,但如果第三方库在库中放置一个new,但在头文件中放置一个delete(或反之亦然),则会失败。
2. 如何确定调用源?如果使用#define拦截new、delete等,则通常使用预处理器符号__FILE__和__LINE__来获取泄漏源。然而,如果您的代码中有“通用”函数,例如CreateString(),那么大多数泄漏将在这些通用函数中报告,这并没有真正帮助您。 另一种选择是在运行时获取调用堆栈。可以使用Windows StackWalk函数轻松完成,但根据我的经验,这非常慢。一个更快的替代方法是直接获取基指针,并依赖于堆栈帧指针(您必须使用/Oy-编译以获取堆栈帧指针)。您可以像这样获取帧(基)指针:_asm mov DWORD PTR [FramePtr], ebp。然后简单地循环,在循环中从((ADDR *)FramePtr)[1]获取指令指针,从FramePtr = ((ADDR *)FramePtr)[0]获取下一个帧指针。
3. 如何在确切时刻报告泄漏?在我的情况下,我希望在应用程序结束时报告泄漏,但是要做到这一点,您需要在应用程序末尾具有泄漏报告机制。这意味着如果您想自己报告泄漏,则需要依赖于全局变量在应用程序末尾被销毁(并在全局变量的析构函数中报告泄漏)。对于服务器类型的应用程序,您可能更感兴趣的是获取两个时间点之间的内存使用差异。
现在是不同的泄漏系统:
  1. C运行时库:在结束时报告泄漏,但没有很好的报告调用堆栈的方法。它拦截new、delete等调用的方式可能会与第三方库(如Qt、Boost等)组合使用时出现问题。

  2. 外部Microsoft工具(如GFlags、UMDH等):它们似乎只能记录两个时间点之间的差异。然而,调用堆栈似乎要好得多,尽管GFlags实用程序可能会在操作系统中设置标志,从而导致应用程序严重减速。

  3. Visual Leak Detector。似乎可以正确找到所有泄漏,但在我的情况下,它不起作用,因为我有一个第三方DLL,它只是在其DllUnload时终止进程(似乎是Windows 7特定的问题)。

  4. 我个人最喜欢的方法(人们肯定不同意我的观点),是编写自己的内存管理器。可以使用全局new和delete运算符轻松拦截(带有上述可能的问题),并且可以像上面描述的那样获取调用堆栈。此替代方案还依赖于能够在应用程序的最后一刻执行代码。

选择替代方案时,我发现以下方面对我的情况非常重要:

  • 我希望它能够在我的应用程序中无缝工作,以便每个开发人员立即收到泄漏通知。如果您将泄漏检查延迟到稍后使用Purify等外部工具进行,则泄漏查找将更加困难。
  • 我希望自动在应用程序结束时报告泄漏。
  • 我希望尽可能多地获取泄漏信息(数据、调用堆栈等)。

希望这有所帮助。


5

这是来自Visual Studio自己的调试CRT的输出,而不是来自Visual Leak Detector的输出。首先确保你正在使用Codeplex上的当前版本,并且在你的项目中已经#include了vld.h。这样你将会获得更加详细的输出。


嗨 Mandrill!不幸的是,这是 Visual Leak Detector 的输出。我正在使用从 CodeProject 网站获取的旧版本,而不是来自 CODEPLEX 的版本... 所以我想我应该更新 + 我会检查设置。重要提示: 你能温柔地确认我写的是真正存在的漏洞吗? - user311906
那不是Visual Leak Detector的输出 -- 你会得到很多输出,其中包括对象分配的调用位置。你正在看到Visual Studio泄漏检测器的输出:http://msdn.microsoft.com/en-us/library/e5ewb1h3%28VS.80%29.aspx - the_mandrill

2

经过调试许多头文件后,我终于明白了。

以下是启用输出中的文件/行号所需完成的步骤:

#define _CRTDBG_MAP_ALLOC
#define _CRTDBG_MAP_ALLOC_NEW

由于某种原因,这两种定义只是告诉我: c:\program files (x86)\microsoft visual studio 10.0\vc\include\crtdbg.h(1116) : {640} normal block at 0x000A5E28, 12 bytes long. 除了泄漏之外...它并没有真正帮助什么。 - Nadav
设置这个会触发以下警告:c:\program files\microsoft visual studio 9.0\vc\include\crtdbg.h(1203) : warning C4985: 'operator new': attributes not present on previous declaration。 - 0x26res

1

你编译时启用了调试信息,并确保泄漏检测器可用的 pdb 文件吗?没有这些信息,它将无法提供行号。


0
你应该使用Valgrind,它非常强大,可以准确地解释你的程序中泄漏的位置。不过你的程序可能需要使用gcc编译...

我知道这个工具,但我恐怕必须坚持使用VC++! :) - user311906
不过你可以在gcc中构建它,无论使用哪个编译器,泄漏都是一样的。 - Patrick
3
假设他所使用的应用程序和库文件能够兼容 Linux/GCC 和 Windows/VC++。 - Roman A. Taycher

0

Rational Purify 可作为 VC++ 的付费插件使用,是一个非常好的泄漏(和其他问题)检测器。我曾经在 Solaris 上经常使用它,它非常易于使用和清晰明了。我也听到其他人对与 Visual Studio 版本一样好的评价,但我从未尝试过。

顺便说一下,我怀疑 Purify 是 Valgrind 的灵感来源,Valgrind 已经被提到过了。


0
如果分配的数字(花括号中的数字)始终相同,则可以使用this could help。基本上,它描述了如何使VC++在尝试使用指定编号进行分配时生成断点。

嗨,SpaceComboy,谢谢!我注意到了;我看到在某些项目中它保持不变,在其他项目中发生了改变。 - user311906
分配号仅计算所有堆分配。如果您的应用程序每次以相同的顺序进行分配,则泄漏分配的数量不会改变,您可以使用它来跟踪问题的起源。这甚至比文件和行号更有用,因为它还会为您提供应用程序的当前状态。 - Björn Pollex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接