如何跟踪C++中的内存分配,尤其是new
/ delete
.对于一个对象,我可以轻松覆盖operator new
,但我不确定如何全局覆盖所有分配,以便它们通过我的自定义new
/ delete
.这应该不是一个大问题,但我不确定应该怎么做(#define new MY_NEW
?).
一旦这个工作,我会认为它足以在分配的指针/位置的某处有一个映射,所以我可以跟踪当前"活动"的所有分配,并且 - 在应用程序结束时 - 检查分配没有被释放的.
好吧,这似乎再次像肯定已经多次完成了几次,所以任何好的库(最好是便携式的)?
我建议你使用valgrind
linux.它会捕获未释放的内存,以及写入未分配内存等其他错误.另一种选择是mudflap,它告诉你没有释放内存.使用-fmudflap -lmudflap
gcc选项,然后启动你的程序MUDFLAP_OPTIONS=-print-leaks ./my_program
.
这是一些非常简单的代码.它不适合复杂的跟踪,但是如果你自己实现它,它的目的是向你展示你原则上如何做.像这样的东西(省略了调用注册的new_handler和其他细节的东西).
templatestruct track_alloc : std::allocator { typedef typename std::allocator ::pointer pointer; typedef typename std::allocator ::size_type size_type; template struct rebind { typedef track_alloc other; }; track_alloc() {} template track_alloc(track_alloc const& u) :std::allocator (u) {} pointer allocate(size_type size, std::allocator ::const_pointer = 0) { void * p = std::malloc(size * sizeof(T)); if(p == 0) { throw std::bad_alloc(); } return static_cast (p); } void deallocate(pointer p, size_type) { std::free(p); } }; typedef std::map< void*, std::size_t, std::less , track_alloc< std::pair > > 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 }
我们必须为我们的map使用我们自己的allocator,因为标准的将使用我们重写的operator new,这将导致无限递归.
确保如果覆盖operator new,则使用映射注册分配.删除由新的放置形式分配的内存也将使用该删除操作符,因此如果您不知道的某些代码重载了不使用您的地图的操作符,则会变得棘手,因为操作符删除会告诉您它没有被分配和使用std::free
释放内存.
另请注意,正如Pax也指出了他的解决方案,这只会显示由使用我们自己定义的运算符new/delete的代码引起的泄漏.因此,如果您想使用它们,请将它们的声明放在标题中,并将其包含在应该观看的所有文件中.
具体来说,使用valgrind的massif工具.与memcheck相反,massif不关心非法使用内存,而是跟踪分配随着时间的推移.它可以很好地"有效"地测量程序的堆内存使用情况.最好的部分是,您不必编写任何代码.尝试:
http://valgrind.org/docs/manual/ms-manual.html
或者,如果你真的不耐烦:
valgrind --tool=massifms_print massif.out. | less
这将为您提供一段时间内的分配图,以及返回大分配所在位置的跟踪图.这个工具最好在Linux上运行,我不知道是否有Windows varient.它不会在OS X上运行
祝好运!
您可以使用http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml上的代码进行以下修改:仅当您有一个大的honkin'源文件时,给出的代码才有效.我把这个问题排在另外一个关于SO的问题(这里).
首先,不要更改stdafx.h,在自己的文件中进行修改.
例如,创建一个单独的头文件mymemory.h并将您的函数原型放入其中(请注意,这没有正文):
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"
在每个需要跟踪内存的源文件中(可能都是它们).因为头文件中没有定义,所以在链接期间不会出现重复,并且因为声明存在,所以也不会得到未定义的引用.
请记住,这不会跟踪您不编译的代码中的内存泄漏(例如,第三方库),但它应该让您了解自己的问题.
好吧,您可以重新实现全局运算符new和delete以提供您想要的功能,但我建议不要这样做,除非这是跟踪内存分配的唯一方法,例如由于您的平台的限制.
内存调试程序可用于大多数常见开发平台.看看PurifyPlus是否适用于Windows和各种Unix或valgrind的商业解决方案,适用于在Linux上运行的开源软件(可能还有其他操作系统,但我只在Linux上使用它).
如果您打算更换全局运算符,请查看本文.