从零开始学C++之重载 operator new 和 operator delete 实现一个简单内存泄漏跟踪器
先来说下实现思路:可以实现一个Trace类,调用 operator new 的时候就将指向分配内存的指针、当前文件、当前行等信息添加进Trace 成员map容器内,在调用operator delete 的时候删除这些信息。定义一个全局Trace 对象,当程序结束,对象析构时判断成员map 是否还有信息,如果有则打印出来,表示已经发生内存泄漏,从输出可以看出是哪一个文件哪一行分配了内存但没有释放掉。
DebugNew.h:
1
2 3 4 5 6 7 8 9 |
#ifndef _DEBUG_NEW_H_
#define _DEBUG_NEW_H_ #ifndef NDEBUG #include "Tracer.h" #define new new(__FILE__, __LINE__) #endif // NDEBUG #endif // _DEBUG_NEW_H_ |
Trace.h:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#ifndef _TRACER_H_
#define _TRACER_H_ #include <map> #ifndef NDEBUG void * operator new(size_t size, const char *file, long line); void operator delete( void *p); void * operator new[](size_t size, const char *file, long line); void operator delete[]( void *p); class Tracer { private: class Entry { public: Entry( const char *file = 0, long line = 0) : file_(file), line_(line) {} const char *File() const { return file_; } long Line() const { return line_; } private: const char *file_; long line_; }; public: Tracer(); ~Tracer(); static bool Ready; void Add( void *p, const char *file, long line); void Remove( void *p); void Dump(); private: std::map< void *, Entry> mapEntry_; }; #endif // NDEBUG #endif // _TRACER_H_ |
Trace.cpp:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
#include <iostream>
#include "Tracer.h" #ifndef NDEBUG bool Tracer::Ready = false; Tracer::Tracer() { Ready = true; } Tracer::~Tracer() { Ready = false; Dump(); } void Tracer::Add( void *p, const char *file, long line) { mapEntry_[p] = Entry(file, line); } void Tracer::Remove( void *p) { std::map< void *, Entry>::iterator it; it = mapEntry_.find(p); if (it != mapEntry_.end()) { mapEntry_.erase(it); } } void Tracer::Dump() { if (mapEntry_.size() > 0) { std::cout << "*** Memory leak(s):" << std::endl; std::map< void *, Entry>::iterator it; for (it = mapEntry_.begin(); it != mapEntry_.end(); ++it) { const char *file = it->second.File(); long line = it->second.Line(); int addr = reinterpret_cast< int>(it->first); std::cout << "0x" << std::hex << addr << ": " << file << ", line " << std::dec << line << std::endl; } std::cout << std::endl; } } Tracer NewTrace; void * operator new(size_t size, const char *file, long line) { void *p = malloc(size); if (Tracer::Ready) { NewTrace.Add(p, file, line); } return p; } void operator delete( void *p) { if (Tracer::Ready) { NewTrace.Remove(p); } free(p); } void * operator new[](size_t size, const char *file, long line) { void *p = malloc(size); if (Tracer::Ready) { NewTrace.Add(p, file, line); } return p; } void operator delete[]( void *p) { if (Tracer::Ready) { NewTrace.Remove(p); } free(p); } #endif // #ifndef NDEBUG |
main.cpp:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream>
using namespace std; #include "DebugNew.h" int main( void) { int *p = new int; /*delete p;*/ int *p2 = new int[ 5]; /*delete[] p2;*/ return 0; } |
程序 #define new new(__FILE__, __LINE__); 是为了利用__FILE__, 和 __LINE__两个宏,分别代表文件名和行数。分别重载了
operator new 和 operator new[] 函数以及对应的delete,更详细的讨论可以参见这里。当全局对象NewTrace 析构时调用Dump成员
函数,如果new 和 delete 没有匹配,那么map将存在泄漏信息,并打印出来。
此外只在Debug版本(没有定义NDEBUG)才跟踪内存泄漏,所以加上#ifndef NDEBUG ... #endif
而由于一般的C++库中可能没有#define new new(__FILE__, __LINE__); 即调用的还是原始的new,但现在程序中并没有重载这种类
型的new和delete函数,故并不能跟踪类似map容器之类的内存泄漏,但一般正常使用C++库容器的话,是不会造成内存泄漏的,
C++库已经实现得比较完善了,至少比我们自己写的程序要好很多。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范