2009-01-13 74 views
27

如何追踪在C++中,特别是那些由new/delete完成的内存分配。对于一个对象,我可以轻松覆盖operator new,但我不确定如何全局覆盖所有分配,因此它们会通过我的自定义new/delete。这应该不是一个大问题,但我不知道这应该如何完成(#define new MY_NEW?)。如何跟踪使用C内存分配++(尤其是新/删除)

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

嗯,这似乎又喜欢的事,肯定已经至少已经做了好几次,所以任何好图书馆外面(最好是便携式的)?

+0

没有通用的现成答案。请提供有关所用操作系统和平台的更多信息。 – kauppi 2009-01-13 10:59:36

+0

我需要一个至少可以在Linux和Windows上运行的解决方案,并且最好在Mac OS上运行。 – Anteru 2009-01-13 12:27:40

回答

26

我建议你在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,使用map来注册您的分配。删除由新的布局形式分配的内存也将使用该删除操作符,因此如果某些代码不知道已经重载了operator new而不使用您的映射,那么它会变得棘手,因为操作符delete会告诉您它没有被分配,使用std::free释放内存。

还请注意,因为Pax也指出他的解决方案,所以只会显示由使用我们自己定义的运算符new/delete的代码导致的泄漏。所以如果你想使用它们,把他们的声明放在一个头文件中,并将其包含在所有应该被监视的文件中。

+0

伟大的职位。我的例子非常有帮助,可以跟踪和修复嵌入式设备中的内存泄漏:) – tkarls 2012-08-16 08:53:55

+0

很好的例子!有一点需要注意,这段代码不是线程安全的,所以在一个多线程环境中(其中`new`和`delete`将会被多个线程调用),你必须保护对'track`映射的访问,的std :: mutex`。 – gbmhunter 2017-10-13 19:20:27

1

不能直接回答你的问题,但如果你真的只是想在节目结束时得到泄露的堆对象的列表,你可能只是valgrind运行程序。

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

+0

是的,我现在使用这些,但我想切换内存分配器(尤其是跟踪各种类别的内存),所以我需要一个自定义的解决方案。 – Anteru 2009-01-13 10:24:59

7

那么,你可以重新实现全球运营商new和delete给你你想要的功能,但我建议不要,除非这是跟踪内存分配的唯一方法,因为你的平台的限制例。

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

如果您在更换全球运营商的意图,看看this article

7

您可以通过以下修改在http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml使用的代码:如果你有一个大的honkin'源文件只给了代码工作。我把这个问题整理成另一个关于SO的问题(here)。

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

制作一个单独的头文件mymemory.h,并把你的函数原型在里面,例如(注意,这没有):

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

也是在这头,把其他原型AddTrack(),DumpUnfreed()等,#定义,类型定义和EXTERN声明:

extern AllocList *allocList; 

然后,在新的mymemory.cpp(其中也包括#的mymemory.h),把实际allocList的定义以及所有的rea l函数(不只是原型)并将该文件添加到您的项目中。

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

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

3

对于我们的Windows平台C++项目,我使用VLD,Visual Leak Detector,它实际上很容易实现,当您的应用程序退出时跟踪和报告内存泄漏 - 它的所有免费和源代码都可用。该系统可以设置为以多种方式(磁盘记录器,IDE,XML等)进行报告,并且对于检测Windows服务中的泄漏是非常宝贵的,这对于调试始终是一项挑战。因此,在您寻找便携式解决方案的同时,如果您希望推出自己的产品,当然可以查看指导来源。希望能帮助到你。

引述网站:

这是一个非常有效的方式,快速 诊断和修复,内存泄漏 C/C++应用程序。

http://dmoulding.googlepages.com/vld

-1

如果你的意思是要做到这一点作为编程练习,它可能给你更多的有识之士来编写自己的智能指针类(ES)来代替,并坚持使用他们在整个这一个项目(或项目的模块)。

3

在Linux中,有至少两个传统方法:

  • 的malloc()和free()(和其他存储相关的功能)是弱符号,这意味着你可以简单地重新实现它们,你的版本将会被使用。有关实施示例:请参阅电围栏。
  • 使用LD_PRELOAD环境变量,可以使用包含在LD_PRELOAD环境变量中的库中的符号覆盖共享库中的符号(弱符号和强符号)符号。如果您使用malloc(),free()和friends编译共享库,则全部设置完成。电围栏再一次证明了这一点。

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

但是请注意,除了这些都是有趣的技术外,我建议使用valgrind来做你想要的任何东西。

0

如果你在linux下开发,最好的工具之一(例如检测内存泄漏,跟踪代码中某些地方的分配情况)是valgrind,特别是它的地块工具。唯一的缺点是程序运行速度较慢(或者较慢),所以它只对调试有用。

0

我注意到很多其他答案都集中在你可以使用的工具上。我已经使用了其中的一些,他们帮助了很多。但是作为编程练习,并且看到您使用C++,您需要覆盖全局新建和删除以及malloc,free和realloc。你会认为只有重写新的和删除就足够了,但std :: string和其他类可能使用malloc,特别是realloc。

然后,一旦你有这个地方,你可以开始添加标题来检查内存覆盖,每个分配记录堆栈跟踪等等。总而言之,我建议你使用这里提到的工具之一,但编写自己的系统可能很有趣。

+0

我严重怀疑std:; string会使用realloc,因为它必须使用提供的allocator,它不支持realloc。 – Anteru 2009-01-13 12:43:35

23

具体来说,使用valgrind的地块工具。与memcheck相反,地块不关心非法使用内存,而是跟踪随着时间的推移分配。它能够有效地测量程序的堆内存使用情况。最好的部分是,你不必编写任何代码。尝试:

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

或者,如果你真的很急:

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

这会给你分配的图形随着时间的推移,和背部走线的大分配内容时发生在哪里。这个工具最好在Linux上运行,我不知道是否有Windows差异。它确实在OS X上工作。

祝你好运!

0

这并不便宜,但我曾经在我的C++日子里发现purify是调试泄漏和其他内存问题的最佳工具(它现在归IBM所有,所以surport下山了)。 Bounds Checker被某些人喜欢,但对于我正在开发的软件并不适用。

0

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

0

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

  • 如果您使用glibc,您可以使用mtrace。它安装一个全局钩子,记录每个glibc的内存分配函数(malloc,realloc,memalign,free以及在它们之上实现的所有东西,比如new/delete)
  • 如果使用Microsoft CRT,则可以查看CRT Debug Heap Details。有实例如何安装的内存分配函数调试版本,让堆统计,发现内存泄漏等
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; 
}