在非托管C / C ++代码中,检测内存泄漏的最佳实践是什么?和编码指南要避免?(好像就是这么简单;)
过去,我们使用了一些愚蠢的方法:每个内存分配调用都有一个计数器递增,释放时递减。在程序结束时,计数器值应为零。
我知道这不是一个好方法,而且有一些问题。(例如,如果您要释放由平台API调用分配的内存,则您的分配计数将与您的释放计数不完全匹配。当然,然后,在调用分配了内存的API调用时,我们会增加计数器。)
我期待您的经验,建议,以及一些简化此工具的参考。
在非托管C / C ++代码中,检测内存泄漏的最佳实践是什么?和编码指南要避免?(好像就是这么简单;)
过去,我们使用了一些愚蠢的方法:每个内存分配调用都有一个计数器递增,释放时递减。在程序结束时,计数器值应为零。
我知道这不是一个好方法,而且有一些问题。(例如,如果您要释放由平台API调用分配的内存,则您的分配计数将与您的释放计数不完全匹配。当然,然后,在调用分配了内存的API调用时,我们会增加计数器。)
我期待您的经验,建议,以及一些简化此工具的参考。
Answers:
如果您的C / C ++代码可移植到* nix,则没有什么比Valgrind好。
如果您使用的是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>
然后您需要在程序退出时调用此命令:
_CrtDumpMemoryLeaks();
另外,如果您的程序并非每次都在同一位置退出,则可以在程序开始时调用它:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
现在,当程序退出时,所有未释放的分配将连同分配它们的文件以及分配发生一起打印在“输出”窗口中。
此策略适用于大多数程序。但是,在某些情况下变得困难或不可能。使用在启动时进行一些初始化的第三方库可能会导致其他对象出现在内存转储中,并且可能难以跟踪泄漏。另外,如果您的任何类具有与任何内存分配例程(例如malloc)同名的成员,则CRT调试宏将引起问题。
上面引用的MSDN链接中还介绍了其他可以使用的技术。
在C ++中:使用RAII。智能指针一样std::unique_ptr
,std::shared_ptr
,std::weak_ptr
是你的朋友。
作为C ++开发人员,这里有一些简单的准则:
至于个人检测内存泄漏,我一直使用Visual Leak Detector,发现它非常有用。
我使用DevStudio已经有很多年了,这让我感到惊讶的是,有多少程序员不了解调试运行时库中提供的内存分析工具。以下是一些入门链接:
跟踪堆分配请求 -特别是有关唯一分配请求号的部分
当然,如果您不使用DevStudio,那么它将不会特别有用。
我很惊讶,没有人提到用于Windows操作系统的DebugDiag。
它适用于发布版本,甚至在客户现场。
(您只需要保留发行版的PDB,并配置DebugDiag以使用Microsoft公共符号服务器)
Visual Leak Detector是一个非常好的工具,尽管它不支持VC9运行时上的调用(例如MSVCR90D.DLL)。
调试模式下的Microsoft VC ++会显示内存泄漏,尽管它不会显示泄漏的位置。
如果您使用的是C ++,你可以随时避免使用新的明确的:你有vector
,string
,auto_ptr
(预C ++ 11,改为unique_ptr
在C ++ 11),unique_ptr
(C ++ 11)和shared_ptr
(C ++ 11)在你的军火库。
当不可避免要使用new时,请尝试将其隐藏在构造函数中(并将delete隐藏在析构函数中);同样适用于第三方API。
如果您使用的是MS VC ++,我可以从代码项目中强烈推荐这个免费工具:Jochen Kalmbach 的泄漏 查找器。
您只需将类添加到您的项目中,然后调用
InitAllocCheck(ACOutput_XML)
DeInitAllocCheck()
您要检查泄漏的代码之前和之后。
构建并运行代码后,Jochen将提供一个简洁的GUI工具,您可以在其中加载生成的.xmlleaks文件,并浏览生成每个泄漏的调用堆栈,以查找有问题的代码行。
Rational的(现在由IBM拥有)PurifyPlus以类似的方式说明了泄漏,但是我发现泄漏查找器工具实际上更易于使用,而且其好处还没有花费数千美元!
如果您使用的是Visual Studio,则可能值得查看Bounds Checker。它不是免费的,但是在发现我的代码中的泄漏方面非常有用。它不仅会导致内存泄漏,还会导致GDI资源泄漏,WinAPI使用错误以及其他问题。它甚至会向您显示泄漏内存的初始化位置,从而更容易追踪泄漏。
我认为这个问题没有简单的答案。您如何真正采用此解决方案取决于您的要求。您是否需要跨平台解决方案?您使用的是new / delete还是malloc / free(或两者都使用)?您是否真的只是在寻找“泄漏”,还是想要更好的保护,例如检测缓冲区溢出(或溢出)?
如果您在Windows端工作,则MS调试运行时库具有一些基本的调试检测功能,并且正如另一个已经指出的那样,您的源代码中可以包含几个包装程序,以帮助进行泄漏检测。寻找可以同时使用new / delete和malloc / free的软件包显然可以为您提供更大的灵活性。
我对Unix方面的了解不足以提供帮助,尽管其他人也提供了帮助。
但是,不仅仅是泄漏检测,还有通过缓冲区溢出(或溢出)来检测内存损坏的概念。我认为,这种调试功能比普通的泄漏检测更加困难。如果您使用的是C ++对象,则这种类型的系统也会更加复杂,因为可以以多种方式删除多态类,从而在确定要删除的真正基址指针时会遇到麻烦。我知道没有一个好的“免费”系统可以对超限提供良好的保护。我们已经编写了一个系统(跨平台),发现它具有很大的挑战性。
我想提供一些我过去使用过的东西:一个基本的泄漏检查器,它是源代码级别的,并且是相当自动的。我之所以给予这个奖励有以下三个原因:
您可能会发现它很有用。
尽管有点残酷,但我不要让我感到尴尬。
即使它与某些win32挂钩相关联,也应易于缓解。
使用它时,必须注意以下new
几点:不要做任何需要依靠在底层代码中的事情,当心关于在checkup.cpp顶部可能遗漏的情况的警告,意识到如果您打开在执行图像转储的代码上(并解决所有问题),您可能会生成一个巨大的文件。
该设计旨在允许您打开和关闭检查器,而无需重新编译包括其标头的所有内容。在要跟踪检查并重新生成一次的位置包括泄漏检查.h。之后,编译带有或不带有LEAKCHECK #define的Leakcheck.cpp,然后重新链接以将其打开和关闭。包含unleakcheck.h将在文件中本地将其关闭。提供了两个宏:CLEARALLOCINFO()将避免在遍历分配不包含泄漏检查.h的代码时报告相同的文件和行。ALLOCFENCE()只是在生成的报告中删除一行,而不进行任何分配。
再次提醒您,我已经有一段时间没有使用它了,您可能需要使用它。我将其放入以说明该想法。如果真的有足够的兴趣,我将愿意举一个示例,在此过程中更新代码,并用更好的名称替换下面URL的内容,其中包括一个语法清晰的清单。
您可以在这里找到它:http : //www.cse.ucsd.edu/~tkammeye/leakcheck.html
对于Linux:尝试使用Google Perftools
Goolge Perftools的优点是有很多工具可以进行类似的分配/免费计数。
我建议从软件验证中使用Memory Validator。事实证明,此工具对帮助我追踪内存泄漏并改善我正在处理的应用程序的内存管理非常有用。
一个非常完整和快速的工具。
Paul Nettle的mmgr是我长期以来最喜欢的工具。您在源文件中包含mmgr.h,定义TEST_MEMORY,它提供了一个文本文件,该文本文件充满了应用程序运行期间发生的内存问题。
在此列表的顶部(当我阅读时)是valgrind。如果您能够在测试系统上重现泄漏,那么Valgrind就是很好的选择。我已经成功地使用了它。
如果您刚刚发现生产系统正在泄漏并且不知道如何在测试中重现该怎么办?在生产系统的状态中会捕获一些错误的证据,这可能足以提供问题的根源,以便您可以重现它。
这就是蒙特卡洛采样的地方。阅读Raymond Chen的博客文章“穷人识别内存泄漏的方法”,然后检查我的实现(假设Linux仅在x86和x86-64上进行了测试)
rmdebug是一个不错的malloc,calloc和reallloc替代品,使用非常简单。与valgrind相比,它要快得多,因此可以广泛地测试代码。当然,它有一些缺点,一旦发现泄漏,您可能仍需要使用valgrind来查找泄漏的位置,并且只能测试直接执行的malloc。如果某个库因您使用错误而泄漏,则rmdebug找不到它。
大多数内存分析器都会将我的大型复杂Windows应用程序减慢到导致结果无用的地步。有一种工具可以很好地发现我的应用程序中的泄漏:UMDH- http : //msdn.microsoft.com/zh-cn/library/ff560206%28VS.85%29.aspx
Mtrace似乎是Linux的标准内置工具。步骤如下: