如何在(非托管)代码中检测/避免内存泄漏?

128

在未经管理的C/C++代码中,如何检测内存泄漏并避免编码指导方针?(好像这很简单 ;))

我们过去使用了一种有点傻的方法:对每个内存分配调用进行计数增加,并在释放时进行减少。在程序结束时,计数值应该为零。

我知道这不是一个好的方式,还有一些陷阱。(例如,如果您释放由平台API调用分配的内存,则您的分配计数将不完全匹配您的释放计数。当然,在调用分配内存的API调用时,我们会增加计数。)

我期待您的经验、建议和可能一些简化此过程的工具的参考。


关于避免内存泄漏,以下帖子提供了一些建议:https://dev59.com/x0XRa4cB1Zd3GeqPoQju - tonylo
我在这里提出了这个问题: https://dev59.com/O3VD5IYBdhLWcg3wTJvF 并且使用Visual Leak Detector 取得了很大的成功。 - Jim Buck
我使用这个工具和Visual Studio一起检测内存泄漏。 http://www.codeproject.com/KB/applications/visualleakdetector.aspx - tiboo
1
你可以搜索Linux下的Valgrind或Windows下的Deleaker,还可以查看Visual Leak Detector... - John Smith
查找内存泄漏请参考此处:http://theunixshell.blogspot.com/2013/11/finding-memory-leaks-on-solaris-is-no.html - Vijay
显示剩余3条评论
29个回答

81
如果你的C/C++代码可以在*nix上移植,那么几乎没有比Valgrind更好的工具了。

1
Valgrind现在也可以在OS X上运行,因此Linux不是您唯一的选择。 - Michael Anderson
1
Valgrind适用于Linux(和OS X)。如果您使用的是Windows - Deleaker - 最好的选择! - John Smith
@JordiBunster:不错!但是基于运行时。对于大型代码库(在我的情况下是用C编写的),您主要会测试程序的设计方式。攻击者可以花费数千个小时阅读代码,以查找内存泄漏漏洞。我希望有一种类似于JavaScript的源代码分析自动化工具。 - user2284570

66

如果您正在使用Visual Studio,Microsoft提供了一些有用的函数来检测和调试内存泄漏。

我建议从这篇文章开始: https://msdn.microsoft.com/zh-cn/library/x98tx3cf(v=vs.140).aspx

以下是这些文章的简要概述。首先,需要包含这些头文件:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

当你的程序退出时,你需要调用这个函数:

Then you need to call this when your program exits:

_CrtDumpMemoryLeaks();

如果您的程序每次退出的位置不同,您可以在程序开头调用以下方法:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

现在当程序退出时,所有未被释放的分配将与它们被分配的文件和分配发生位置一起打印在“输出窗口”中。

这种策略适用于大多数程序。然而,在某些情况下,使用第三方库在启动时进行初始化可能会导致其他对象出现在内存转储中,并且可能使跟踪泄漏变得困难或不可能。另外,如果您的任何类具有与任何内存分配例程(如malloc)相同名称的成员,则CRT调试宏将会引起问题。

MSDN链接中提到的还有其他可用的技术。


关于这个方法的说明:似乎只有在使用纯C的malloc和free时才有效。如果你使用C++的new和delete,将不会生成包括行号的详细报告。 - Zach
2
@Zach:实际上你也可以让这个工作(对于任何你自己编译的代码),请参见http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/5a98b72e-c927-4f4b-9441-8a73575dfb10/中的被接受的答案。 - Roman Starkov
这个在发布模式下也能正常工作吗? - J V
1
@user3152463 不行。根据文档,它只适用于调试构建:https://msdn.microsoft.com/zh-cn/library/e5ewb1h3(v=vs.71).aspx - Dusty Campbell
这一行代码是错误的:#define CRTDBG_MAP_ALLOC应该修改为:#define _CRTDBG_MAP_ALLOC - Fallso
如果您正在使用C++,可以创建一个类,其中其析构函数调用“_CrtDumpMemoryLeaks”,并创建一个静态实例。当应用程序退出时将调用析构函数。 - David Haim

37
在C++中:使用RAII技术。智能指针如std::unique_ptr, std::shared_ptr, std::weak_ptr是你的好帮手。

1
而 std:vector 是一个很好的替代品,当数组(缓冲区)在同一函数中被分配和释放时。 - KJAWolf
4
至少 std::auto_ptr 和 boost::shared_ptr 仍然容易发生内存泄漏。 - Jasper Bekkers
5
只有在使用不当的情况下才会出现问题,但我必须承认,对于 std::auto_ptr 来说,使用不当是相当容易的。 - Leon Timmermans
2
虽然这是编码标准的好建议,但它并没有回答这个问题。即使使用 shared_ptr,也可能会在循环依赖中导致泄漏。而且你可以通过无限缓存策略来实现“泄漏”,这甚至适用于垃圾收集语言。 - CashCow
@CashCow:你说得对。虽然我还没有在实践中看到过它,但这可能是因为我很少使用它们。引用下面的答案,“仅在绝对必要时使用指针”。 - Leon Timmermans

29
作为一名C++开发者,这里有一些简单的指南:
  1. 仅在必要时使用指针。
  2. 如果需要指针,请确认是否可以使用智能指针
  3. 使用创建者模式。
就检测内存泄漏而言,我个人一直使用Visual Leak Detector,发现它非常有用。

2
Visual Leak Detector已经迁移到新的网站http://vld.codeplex.com/。 - KindDragon
VLD是一个非常好的泄漏检测器 - 我强烈推荐给所有使用VC++的人。 - Javid
1
+1 对于第一点。这是绝对的基本要素。不幸的是,我发现一些最大的“C ++”库倾向于避免堆栈分配和/或RAII,而是选择到处使用指针,通常没有明显的原因。因此,它们最终成为“带类的C”,而不是真正的C ++。 - underscore_d

16

我已经使用DevStudio很多年了,但总是惊讶于有多少程序员不知道在调试运行时库中可用的内存分析工具。以下是一些入门链接:

跟踪堆分配请求 - 特别是关于唯一分配请求号的部分

_CrtSetDbgFlag

_CrtSetBreakAlloc

当然,如果你不使用 DevStudio,则这些可能没有什么帮助。


10

我很惊讶没有人提到适用于Windows操作系统的DebugDiag
它可以在发布版本上运行,甚至可以在客户现场使用。
(您只需要保留发布版本的PDB文件,并配置DebugDiag使用Microsoft公共符号服务器)


3
链接已失效,请使用以下链接查看文档: http://support.microsoft.com/kb/2580960 下载请访问以下链接: http://www.microsoft.com/en-us/download/details.aspx?id=26798 - JPaget

7

Visual Leak Detector是一个非常好的工具,尽管它不支持VC9运行时的调用(例如MSVCR90D.DLL)。


1
这个工具非常完美!它节省了你使用_MSDN中描述的_CrtDumpMemoryLeaks();和其他方法的麻烦。只需一个包含文件,它就可以暴露所有内容!甚至可以在旧的C库中运行! - m_pGladiator
新版本(适用于VS2013)在这里:http://vld.codeplex.com/ - Dženan

7

Microsoft VC++在调试模式下会显示内存泄漏,但不会显示泄漏的位置。

如果您正在使用C ++,则始终可以避免显式使用new:您可以使用vectorstringauto_ptr(C++11之前的版本;在C++11中由unique_ptr替代)、unique_ptr(C++11)和shared_ptr(C++11)。

当无法避免使用new时,请尝试将其隐藏在构造函数中(并在析构函数中隐藏delete);对于第三方API也是同样适用。


1
不要忘记3或5的规则。 - Humam Helfawi

4
如果您正在使用MS VC ++,我强烈推荐来自codeproject的这个免费工具: leakfinder,作者是Jochen Kalmbach。
您只需要将该类添加到您的项目中,然后调用。
InitAllocCheck(ACOutput_XML)
DeInitAllocCheck()

在你想要检查泄漏的代码前后,使用以下内容。构建并运行代码后,Jochen提供了一个整洁的GUI工具,您可以加载生成的.xmlleaks文件,并浏览调用堆栈,找到产生每个泄漏的有问题代码行。Rational(现在由IBM拥有)的PurifyPlus以类似的方式显示泄漏,但我发现leakfinder工具实际上更容易使用,并且没有成千上万美元的成本!

1
我检查了LeakFinder,看起来还不错,但是提醒一下,它不能直接用于x64,因为它包含内联汇编。 - Zach

4

存在着各种可替换的“malloc”库,它们允许您在结束时调用一个函数,并告诉您所有未释放的内存以及很多情况下是谁首先使用了 malloc(或 new)。


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