关于c ++:如何检测/避免(非托管)代码中的内存泄漏?

关于c ++:如何检测/避免(非托管)代码中的内存泄漏?

How do you detect/avoid Memory leaks in your (Unmanaged) code?

在非托管C / C ++代码中,检测内存泄漏的最佳实践是什么? 和编码指南要避免? (好像就是这么简单;)

过去,我们使用了一些愚蠢的方法:每个内存分配调用都有一个计数器递增,释放时递减。 在程序结束时,计数器值应为零。

我知道这不是一个好方法,而且有一些问题。 (例如,如果您要释放由平台API调用分配的内存,则您的分配计数将与您的释放计数不完全匹配。当然,然后,在调用分配了内存的API调用时,我们会增加计数器。)

我期待您的经验,建议,以及一些简化此工具的参考。


如果您的C / C ++代码可移植到* nix,则没有什么比Valgrind好。


如果您使用的是Visual Studio,Microsoft提供了一些有用的功能来检测和调试内存泄漏。

我将从本文开始:
https://msdn.microsoft.com/zh-CN/library/x98tx3cf(v=vs.140).aspx

这是这些文章的快速摘要。首先,包括以下标头:

1
2
3
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

然后,您需要在程序退出时调用此命令:

1
_CrtDumpMemoryLeaks();

另外,如果您的程序并非每次都在同一位置退出,则可以在程序开始时调用它:

1
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

现在,当程序退出时,所有未释放的分配将连同在其中分配的文件以及分配发生一起打印在"输出"窗口中。

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

上面引用的MSDN链接中还介绍了其他可以使用的技术。


在C ++中:使用RAII。 std::unique_ptrstd::shared_ptrstd::weak_ptr等智能指针是您的朋友。


作为C ++开发人员,这里有一些简单的准则:

  • 仅在绝对必要时使用指针
  • 如果需要指针,请仔细检查是否有可能使用SmartPointer
  • 使用GRASP Creator模式。
  • 至于个人检测内存泄漏,我一直使用Visual Leak Detector,发现它非常有用。


    我使用DevStudio已经有很多年了,它让我感到惊讶的是,有多少程序员不了解调试运行时库中提供的内存分析工具。以下是一些入门链接:

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

    _CrtSetDbgFlag

    _CrtSetBreakAlloc

    当然,如果您不使用DevStudio,那么它将不会特别有用。


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


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


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

    如果您使用的是C ++,则始终可以避免显式地使用new:您有vectorstringauto_ptr(在C ++ 11之前;在C ++ 11中被unique_ptr替换),unique_ptr(C ++ 11)和shared_ptr(C ++ 11)。

    当不可避免要使用new时,请尝试将其隐藏在构造函数中(并在delete构造函数中隐藏delete);同样适用于第三方API。


    那里有各种各样的替换" malloc"库,这些库可让您最后调用一个函数,它将告诉您所有未释放的内存,并且在许多情况下,它们是谁首先分配(或新分配)了它。


    如果您使用的是MS VC ++,我可以从代码项目中强烈推荐此免费工具:
    Jochen Kalmbach的检漏仪。

    您只需将类添加到您的项目中,然后调用

    1
    2
    InitAllocCheck(ACOutput_XML)
    DeInitAllocCheck()

    您想要检查泄漏的代码之前和之后。

    构建并运行代码后,Jochen将提供一个简洁的GUI工具,您可以在其中加载生成的.xmlleaks文件,并浏览生成每个泄漏的调用堆栈,以查找有害的代码行。

    Rational的(现在由IBM拥有)PurifyPlus以类似的方式说明了泄漏,但是我发现泄漏查找器工具实际上更易于使用,而且它的好处是不需要花费数千美元!


    如果您使用的是Visual Studio,则可能值得研究Bounds Checker。它不是免费的,但是在发现我的代码中的泄漏方面非常有用。它不仅会导致内存泄漏,还会导致GDI资源泄漏,WinAPI使用错误以及其他问题。它甚至可以向您显示泄漏内存的初始化位置,从而更容易追踪泄漏。


    我自己从未使用过它,但是我的C朋友告诉我Purify。


    我想提供一些我过去使用过的东西:一个基本的泄漏检查器,它是源代码级别的,并且是相当自动的。
    我之所以给予这个奖励有以下三个原因:

  • 您可能会发现它很有用。

  • 尽管有点残酷,但我不让我感到尴尬。

  • 即使它与某些win32挂钩相关联,也应易于缓解。

  • 使用它时,必须注意以下几点:不要做任何需要依靠基础代码中的new的事情,当心关于在checkup.cpp顶部可能遗漏的情况的警告,请意识到如果打开(并解决所有问题)执行图像转储的代码,则可能会生成一个巨大的文件。

    该设计旨在允许您打开和关闭检查器,而无需重新编译包括其标头的所有内容。在要跟踪检查并重新生成一次的位置包括泄漏检查.h。之后,编译带有或不带有LEAKCHECK#define'd的leakcheck.cpp,然后重新链接以将其打开和关闭。包含unleakcheck.h将在文件中本地将其关闭。提供了两个宏:CLEARALLOCINFO()将避免在遍历分配不包含泄漏检查.h的代码时报告相同的文件和行。 ALLOCFENCE()只是在生成的报告中删除一行,而不进行任何分配。

    再次提醒您,我已经有一段时间没有使用它了,您可能需要使用它。我将其放入以说明该想法。如果真的有足够的兴趣,我将愿意举一个示例,在此过程中更新代码,并用更好的名称替换下面URL的内容,其中包括一个语法清晰的清单。

    您可以在这里找到它:http://www.cse.ucsd.edu/~tkammeye/leakcheck.html


    您是通过插值自己的syscall函数(记录调用,然后将调用传递给实函数)来计数分配和释放吗?

    这是跟踪来自未编写代码的调用的唯一方法。

    查看ld.so的手册页。或在某些系统上为ld.so.1。

    也可以使用Google LD_PRELOAD,您会在www.itworld.com上找到一些有趣的文章来解释该技术。


    我认为这个问题没有简单的答案。您如何真正采用该解决方案取决于您的要求。您是否需要跨平台解决方案?您使用的是new / delete还是malloc / free(或两者都使用)?您是否真的只是在寻找"泄漏",还是想要更好的保护,例如检测缓冲区溢出(或溢出)?

    如果您在Windows端工作,则MS调试运行时库具有一些基本的调试检测功能,并且正如另一个已经指出的那样,您的源代码中可以包含几个包装程序,以帮助进行泄漏检测。寻找一个可以同时使用new / delete和malloc / free的软件包,显然可以为您提供更大的灵活性。

    我对Unix方面的了解不足以提供帮助,尽管其他人也提供了帮助。

    但是,不仅仅是泄漏检测,还有通过缓冲区溢出(或溢出)来检测内存损坏的概念。我认为,这种调试功能比普通的泄漏检测更加困难。如果您使用的是C ++对象,则这种类型的系统也会更加复杂,因为可以以多种方式删除多态类,从而在确定要删除的真正基址指针时会遇到麻烦。我知道没有一个好的"免费"系统可以对超限提供良好的保护。我们已经编写了一个系统(跨平台),发现它具有很大的挑战性。


    对于Linux:
    试用Google Perftools

    Goolge Perftools的优点是有很多工具可以进行类似的分配/免费计数:

    • 相当快(与valgrind相比:非常快)
    • 带有漂亮的结果图形显示
    • 还有其他有用的功能:cpu分析,内存使用分析...

    防止泄漏的最佳方法是最小化malloc使用的程序结构。从编程的角度来看,这不仅是件好事,而且还可以提高性能和可维护性。我不是在谈论使用其他东西来代替malloc,而是在重用对象并在传递的所有对象上保留非常明确的标签,而不是像那些经常在带有垃圾收集器的语言中习惯的那样分配随意的对象像Java。

    例如,我正在处理的程序具有一堆代表图像数据的框架对象。每个框架对象都有子数据,框架的析构函数将释放这些子数据。该程序保留所有已分配帧的列表,并且在需要新帧时,检查未使用帧对象的列表,以查看它是否可以重用现有帧而不是分配新帧。关闭时,它仅遍历列表,释放所有内容。


    我建议从软件验证中使用Memory Validator。
    事实证明,该工具对帮助我追踪内存泄漏并改善我正在处理的应用程序的内存管理具有无价的帮助。

    一个非常完整和快速的工具。


    Paul Nettle的mmgr是我长期以来最喜欢的工具。您在源文件中包含mmgr.h,定义TEST_MEMORY,它提供了一个文本文件,其中充满了应用程序运行期间发生的内存问题。


    至少对于MS VC ++,C运行时库具有一些我发现过去有用的功能。检查MSDN帮助中的_Crt*功能。


    通用编码指南:

    • 资源应在分配它们的同一"层"(功能/类/库)中释放。
    • 如果无法做到这一点,请尝试使用一些自动释放(增加共享指针...)

    在Motorola手机操作系统上,我们劫持了内存分配库以观察所有内存分配。它有助于发现内存分配的许多问题。
    由于预防胜于治疗,因此我建议使用静态分析工具,例如Klockwork或PC-Lint


    内存调试工具值得一试,但是多年来,我发现可以使用两个简单的方法来防止大多数内存/资源泄漏被编码。

  • 在为要分配的资源编写获取代码之后,立即编写发布代码。使用这种方法,更难以"忘记",并且在某种意义上迫使人们认真考虑资源的生命周期,这些资源的生命周期是预先使用而不是暂时使用。

  • 尽量少使用return。如果可能,分配的内容只能在一个地方释放。资源获取与释放之间的条件路径应设计得尽可能简单明了。


  • 在此列表的顶部(当我阅读时)是valgrind。如果您能够在测试系统上重现泄漏,则Valgrind非常有用。我已经成功地使用了它。

    如果您刚刚发现生产系统正在泄漏并且不知道如何在测试中重现该怎么办?在生产系统的状态下会捕获一些错误的证据,这可能足以提供问题的根源,以便您可以重现它。

    这就是蒙特卡洛采样的地方。阅读Raymond Chen的博客文章,
    "穷人识别内存泄漏的方式",然后检查我的实现(假定Linux,仅在x86和x86-64上进行了测试)

    http://github.com/tialaramex/leakdice/tree/master


    大多数内存探查器都会将我的大型复杂Windows应用程序减慢到导致结果无用的地步。有一种工具可以很好地发现我的应用程序中的泄漏:UMDH-http://msdn.microsoft.com/zh-cn/library/ff560206%28VS.85%29.aspx


    Valgrind是Linux的不错选择。在MacOS X下,您可以启用MallocDebug库,该库具有用于调试内存分配问题的多个选项(请参阅malloc联机帮助页,"环境"部分包含相关详细信息)。 OS X SDK还包括一个称为MallocDebug的工具(通常安装在/ Developer / Applications / Performance Tools /中),该工具可以帮助您监视使用情况和泄漏。


    rmdebug是一个不错的malloc,calloc和reallloc替代品,使用起来非常简单。与valgrind相比,它要快得多,因此可以广泛地测试代码。当然,它有一些缺点,一旦发现泄漏,您可能仍需要使用valgrind来查找泄漏的位置,并且只能测试直接执行的malloc。如果某个库因使用不正确而泄漏,则rmdebug找不到。

    http://www.hexco.de/rmdebug/


    检测:

    调试CRT

    避免:

    Boehm GC智能指针


    Mtrace似乎是Linux的标准内置工具。这些步骤是:

  • 在bash中设置环境变量MALLOC_TRACE
    MALLOC_TRACE = / tmp / mtrace.dat
    导出MALLOC_TRACE;
  • 将#include 添加到主源文件的顶部
  • 添加mtrace();在main和muntrace()的开头;在底部(在return语句之前)
  • 使用-g开关编译程序以获取调试信息
  • 运行程序
  • 使用mtrace your_prog_exe_name /tmp/mtrace.dat显示泄漏信息
    (我必须先使用yum install glibc_utils在我的fedora系统上安装mtrace perl脚本)

  • 推荐阅读