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

时间:2009-01-13 10:13:39

标签: c++ debugging memory-management

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

一旦这个工作,我会认为它足以在分配的指针/位置的某处有一个地图,所以我可以跟踪当前“活动”的所有分配和 - 在应用程序结束时 - 检查尚未释放的分配。

嗯,这看起来好像至少已经好几次了,所以任何好的库(最好是便携式的)?

17 个答案:

答案 0 :(得分:26)

我建议你使用valgrind用于linux。它会捕获未释放的内存,以及写入未分配内存等其他错误。另一种选择是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,则使用地图注册分配。删除由新的放置形式分配的内存也将使用该删除操作符,因此如果您不知道的某些代码重载了不使用您的地图的操作符,它会变得棘手,因为操作符删除会告诉您它没有被分配和使用std::free释放内存。

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

答案 1 :(得分:24)

具体来说,请使用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 varient。 在OS X上工作。

祝你好运!

答案 2 :(得分:9)

您可以使用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()等,以及#define,typedef和extern语句:

extern AllocList *allocList;

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

然后,#include "mymemory.h"在您需要跟踪内存的每个源文件中(可能都是它们)。因为头文件中没有定义,所以在链接期间不会出现重复,并且因为声明存在,所以也不会得到未定义的引用。

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

答案 3 :(得分:7)

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

内存调试程序适用于大多数常见开发平台。查看PurifyPlus以获取适用于Windows和各种Unix的商业解决方案,或valgrind查看适用于Linux(可能还有其他操作系统但我只使用过它的开源源代码)的商业解决方案的Linux)。

如果您打算更换全局运营商,请查看this article

答案 4 :(得分:3)

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

引用该网站:

  

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

http://dmoulding.googlepages.com/vld

答案 5 :(得分:3)

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

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

因此,您不仅可以捕获new和delete,还可以捕获C风格的内存分配函数。我还没有在Windows上做过这个,但我已经看到了重写DLL如何链接的方法(虽然我记得它们有点笨拙)。

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

答案 6 :(得分:3)

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

您无需为DebugDiag工作提供程序。

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

虽然它不是最简单或最直观的程序!请确保您获取有关如何使用它的教程和说明。

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

答案 9 :(得分:0)

#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++)尝试增加字符串的长度。

答案 10 :(得分:0)

检查这个非常方便的代码,而不是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;
}

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

它并不便宜,但我曾经在我的C ++时代发现purify是调试泄漏和其他内存问题的最佳工具(同样它现在由IBM拥有,所以surport下山)。 Bounds Checker被某些人所喜欢,但对我正在开发的软件效果不佳。

答案 14 :(得分:0)

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

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

然后,一旦你有了这个,你就可以开始添加标题来检查内存覆盖,记录每个分配的堆栈跟踪等等。

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

答案 15 :(得分:0)

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

答案 16 :(得分:-1)

如果您打算将此作为编程练习,那么它可能会让您更深入地编写自己的智能指针类,并在整个项目(或项目模块)中始终使用它们。 / p>