如何在 C++ 中跟踪内存分配(尤其是新建/删除)

Posted

技术标签:

【中文标题】如何在 C++ 中跟踪内存分配(尤其是新建/删除)【英文标题】:How to track memory allocations in C++ (especially new/delete) 【发布时间】:2010-10-01 02:40:34 【问题描述】:

如何在 C++ 中跟踪内存分配,尤其是由 new/delete 完成的内存分配。对于一个对象,我可以轻松地覆盖operator new,但我不确定如何全局覆盖所有分配,因此它们会通过我的自定义new/delete。这应该不是什么大问题,但我不确定这应该怎么做(#define new MY_NEW?)。

一旦这工作,我会假设在某处有一个分配指针/位置的映射就足够了,所以我可以跟踪所有当前“活动”的分配 - 在应用程序结束时 -检查尚未释放的分配。

嗯,这似乎又是一件至少已经完成了几次的事情,所以那里有什么好的库(最好是便携式的)?

【问题讨论】:

没有通用的现成答案。请提供有关所用操作系统和平台的更多信息。 我需要一个至少适用于 Linux 和 Windows,最好也适用于 Mac OS 的解决方案。 【参考方案1】:
#include<iostream>

void * operator new(size_t size)

    std::cout<<"Allocating:"<<size<<std::endl;
  return malloc (size);


void operator delete(void *ptr)

    std::cout<<"Deleting:"<<ptr<<std::endl;
    free(ptr);


int main() 
    std::string ss("1234567890123456");

如果你没有看到重载的运算符,(你可能在不同的编译器上,然后是我的 g++)尝试增加字符串的长度。

【讨论】:

【参考方案2】:

检查这个小巧方便的代码,现在使用 NEW 代替 new 并跟踪 NewHelper 构造函数中的所有分配:

#include <iostream>

class NewHelper

   private :
    void* addr = nullptr;
       public :
       NewHelper(void * addr_)
       
          addr = addr_;
          std::cout<<addr<<std::endl;
       
       template <class T>
       operator T ()
       
           return (T)addr;
       
;
#define NEW (NewHelper)(void*)new
int main()

  int * i = NEW int(0);
 return 0;

【讨论】:

这不会跟踪任何库代码的分配。此外,您的 (void*) 牺牲了我们使用 new 获得的类型安全性。 编译器将使用 NewHelper::operator T 重新转换类型,无论如何,我已经编写了一个 fullc/c++ 内存跟踪器来跟踪文件和 stdlib 文件中的每一个分配......,我可以如果有人感兴趣,请出售它,功能: - 记录所有从未释放的分配的堆栈跟踪:- 记录所有分配的堆栈跟踪多次免费 - 分配无效的堆栈跟踪 free() ... - 显示属性构造器中发生的所有分配的堆栈跟踪当父对象被分配但从未被删除时(未调用构造函数)【参考方案3】:

如果我需要一个工具,我通常会从我的编译器/标准库提供的工具开始。

如果你使用 glibc,你可以使用mtrace。它安装了一个全局挂钩来记录每个 glibc 内存分配函数(malloc、realloc、memalign、free,以及在它们之上实现的所有内容,例如 new/delete) 如果您使用 Microsoft CRT,您可以查看 CRT Debug Heap Details。有一些示例如何安装调试版本的内存分配函数、获取堆统计信息、查找内存泄漏等。

【讨论】:

【参考方案4】:

您可以使用将 link 中给出的头文件 (MemTracker.h) 添加到您的解决方案中,以跟踪 C 和 C++ 中的内存分配/释放。它显示您是否有内存泄漏以及哪一行代码对此负责。

【讨论】:

【参考方案5】:

它并不便宜,但在我的 C++ 时代,我曾经发现 purify 是调试泄漏和其他内存问题的最佳工具(现在它归 IBM 所有,所以支持下降了)。 Bounds Checker 受到了一些人的喜欢,但对我正在开发的软件效果不佳。

【讨论】:

【参考方案6】:

具体来说,使用 valgrind 的 massif 工具。与 memcheck 不同,massif 不关心内存的非法使用,而是跟踪一段时间内的分配。它在“有效”测量程序的堆内存使用方面做得很好。最好的部分是,您不必编写任何代码。试试:

http://valgrind.org/docs/manual/ms-manual.html

或者如果你真的不耐烦:

valgrind --tool=massif <executable> <args>
ms_print massif.out.<pid> | less

这将为您提供一段时间内的分配图表,并追溯大分配发生的位置。这个工具最好在Linux上运行,不知道有没有Windows版本。它确实在 OS X 上工作。

祝你好运!

【讨论】:

不错,又一个我不知道的 valgrind 工具!【参考方案7】:

您可以使用http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml 的代码并进行以下修改:给出的代码仅在您有一个大的honkin 源文件时才有效。我整理了这个关于 SO (here) 的另一个问题。

首先,不要更改 stdafx.h,在您自己的文件中进行修改。

制作一个单独的头文件 mymemory.h 并将你的函数原型放入其中,例如(注意这里没有 body):

inline void * __cdecl operator new(unsigned int size,
    const char *file, int line);

同样在该标头中,放置 AddTrack()、DumpUnfreed() 等的其他原型,以及 #defines、typedef 和 extern 语句:

extern AllocList *allocList;

然后,在一个新的 mymemory.cpp(也是 #include 的 mymemory.h)中,将 allocList 的实际定义与所有实际函数(不仅仅是原型)一起放入,并将该文件添加到您的项目中。

然后,#include "mymemory.h" 在您需要跟踪内存的每个源文件中(可能全部)。因为头文件中没有定义,所以在链接过程中不会出现重复,而且由于声明存在,所以也不会得到未定义的引用。

请记住,这不会跟踪您未编译的代码(例如第三方库)中的内存泄漏,但它应该让您了解自己的问题。

【讨论】:

【参考方案8】:

如果您在 Windows 下开发,免费工具 DebugDiag 将有助于查找内存和处理泄漏。

您无需增强您的程序即可使 DebugDiag 工作。

http://www.microsoft.com/downloads/details.aspx?FamilyID=28BD5941-C458-46F1-B24D-F60151D875A3&displaylang=en

虽然它不是最简单或最直观的程序!请务必在 Google 上搜索有关如何使用它的教程和说明。

【讨论】:

【参考方案9】:

我建议您在 linux 上使用 valgrind。它将捕获未释放的内存,以及写入未分配内存等其他错误。另一个选项是 mudflap,它也告诉你没有释放内存。在 gcc 中使用 -fmudflap -lmudflap 选项,然后使用 MUDFLAP_OPTIONS=-print-leaks ./my_program 启动您的程序。

这里有一些非常简单的代码。它不适合复杂的跟踪,但旨在向您展示原则上您将如何做到这一点,如果您要自己实现它。像这样(省略了调用已注册的 new_handler 和其他详细信息的内容)。

template<typename T>
struct track_alloc : std::allocator<T> 
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::size_type size_type;

    template<typename U>
    struct rebind 
        typedef track_alloc<U> other;
    ;

    track_alloc() 

    template<typename U>
    track_alloc(track_alloc<U> const& u)
        :std::allocator<T>(u) 

    pointer allocate(size_type size, 
                     std::allocator<void>::const_pointer = 0) 
        void * p = std::malloc(size * sizeof(T));
        if(p == 0) 
            throw std::bad_alloc();
        
        return static_cast<pointer>(p);
    

    void deallocate(pointer p, size_type) 
        std::free(p);
    
;

typedef std::map< void*, std::size_t, std::less<void*>, 
                  track_alloc< std::pair<void* const, std::size_t> > > track_type;

struct track_printer 
    track_type * track;
    track_printer(track_type * track):track(track) 
    ~track_printer() 
        track_type::const_iterator it = track->begin();
        while(it != track->end()) 
            std::cerr << "TRACK: leaked at " << it->first << ", "
                      << it->second << " bytes\n";
            ++it;
        
    
;

track_type * get_map() 
    // don't use normal new to avoid infinite recursion.
    static track_type * track = new (std::malloc(sizeof *track)) 
        track_type;
    static track_printer printer(track);
    return track;


void * operator new(std::size_t size) throw(std::bad_alloc) 
    // we are required to return non-null
    void * mem = std::malloc(size == 0 ? 1 : size);
    if(mem == 0) 
        throw std::bad_alloc();
    
    (*get_map())[mem] = size;
    return mem;


void operator delete(void * mem) throw() 
    if(get_map()->erase(mem) == 0) 
        // this indicates a serious bug
        std::cerr << "bug: memory at " 
                  << mem << " wasn't allocated by us\n";
    
    std::free(mem);


int main() 
    std::string *s = new std::string;
        // will print something like: TRACK: leaked at 0x9564008, 4 bytes

我们必须为我们的地图使用我们自己的分配器,因为标准分配器将使用我们覆盖的运算符 new,这将导致无限递归。

确保如果您覆盖 operator new,您使用地图来注册您的分配。删除由 new 的放置形式分配的内存也将使用该 delete 运算符,因此如果您不知道的某些代码重载了 operator new 而不使用您的 map,这可能会变得很棘手,因为 operator delete 会告诉您它没有被分配并且使用std::free 释放内存。

还请注意,正如 Pax 也为他的解决方案指出的那样,这只会显示由使用我们自己定义的运算符 new/delete 的代码引起的泄漏。因此,如果您想使用它们,请将它们的声明放在头文件中,并将其包含在所有应查看的文件中。

【讨论】:

很棒的帖子。您的示例对我很有帮助,可以跟踪和修复嵌入式设备中的内存泄漏:) 好例子!需要注意的一点是,此代码不是线程安全的,因此在多线程环境中(从多个线程调用 newdelete)您必须使用 @ 保护对 track 映射的访问987654329@.【参考方案10】:

我注意到很多其他答案都集中在您可以使用哪些工具上。我已经使用了其中一些,它们帮助很大。

但作为编程练习,并且看到您使用 c++,您将需要覆盖全局 new 和 delete,以及 malloc、free 和 realloc。你会认为只覆盖 new 和 delete 就足够了,但是 std::string 和其他类可能会使用 malloc ,尤其是 realloc。

然后,一旦你准备好了,你就可以开始添加头来检查内存覆盖,记录每次分配的堆栈跟踪等等。

总而言之,我建议您使用此处提到的工具之一,但编写自己的系统可能会很有趣。

【讨论】:

我严重怀疑 std:;string 是否会使用 realloc,因为它必须使用不支持 realloc 的提供的分配器。【参考方案11】:

如果您在 linux 下开发,最好的工具之一(例如检测内存泄漏、跟踪在某些代码位置完成的分配)是 valgrind,尤其是它的 massif 工具。唯一的缺点是程序运行速度较慢(或慢得多),因此仅对调试有用。

【讨论】:

【参考方案12】:

在 Linux 上,至少有两种传统方法:

malloc() 和 free()(以及其他与内存相关的函数)是弱符号,这意味着您可以简单地重新实现它们并使用您的版本。有关实施示例:请参阅电围栏。 使用 LD_PRELOAD 环境变量,您可以使用 LD_PRELOAD 环境变量中包含的库中的符号覆盖共享库中的符号(弱符号和强符号)。如果你用 malloc()、free() 和朋友编译一个共享库,你就准备好了。电子围栏再次证明了这一点。

因此,您不仅可以捕获 new 和 delete,还可以捕获 C 样式的内存分配函数。我还没有在 Windows 上这样做,但我也看到了重写 DLL 如何在那里链接的方法(尽管我记得它们有点笨拙)。

但是请注意,除了这些是有趣的技术之外,我建议使用 valgrind 来做你想做的最重要的事情。

【讨论】:

【参考方案13】:

如果您打算将此作为编程练习,则改为编写自己的智能指针类,并在整个项目(或项目的模块)中始终如一地使用它们可能会给您更多的洞察力。

【讨论】:

【参考方案14】:

对于我们的 Windows 平台 C++ 项目,我使用 VLD,Visual Leak Detector,它几乎太容易实现了,它可以在您的应用程序退出时跟踪和报告内存泄漏 - 最好的是它是免费的,并且源代码可用。该系统可以设置为以多种方式进行报告(磁盘记录器、IDE、XML 等),并且对于检测 Windows 服务中的泄漏非常有用,而这些泄漏始终是调试的挑战。因此,当您正在寻找便携式解决方案时,如果您想自己动手,您当然可以查看源代码以获取指导。希望对您有所帮助。

引用网站:

这是一种非常有效的快速 诊断并修复内存泄漏 C/C++ 应用程序。

http://dmoulding.googlepages.com/vld

【讨论】:

【参考方案15】:

好吧,您可以重新实现全局运算符 new 和 delete 以提供您想要的功能,但我建议您不要这样做,除非这是跟踪内存分配的唯一方法,例如由于您的平台限制.

内存调试器可用于大多数常见的开发平台。查看PurifyPlus 了解适用于 Windows 和各种 Unix 的商业解决方案,或查看valgrind 了解适用于 Linux(以及可能的其他操作系统,但我只在 Linux 上使用过)的开源解决方案。

如果您打算替换全局运算符,请查看this article。

【讨论】:

【参考方案16】:

不直接回答您的问题,但如果您真的只是想在程序结束时获取泄漏的堆对象列表,您可以使用valgrind 运行程序。

对于 MS VS,您可以使用 the Debug CRT Heap。不像valgrind那么简单,这里解释的有点多,但可以做你想做的。

【讨论】:

是的,我现在正在使用这些,但是我想切换内存分配器(特别是跟踪各种类别的内存),所以我需要一个自定义的解决方案。

以上是关于如何在 C++ 中跟踪内存分配(尤其是新建/删除)的主要内容,如果未能解决你的问题,请参考以下文章

跟踪 C++ 内存分配

C++中内存分配问题

如何在 C++ 中动态分配数组

如何从 c++ 的方面(方面 c++)释放或删除函数中的分配对象?

C++ 删除不会释放所有内存 (Windows)

C++ 检测内存分配