C程序中最可怕的事情就是碰到内存泄漏或者内存错误,特别是对于大型的项目而言要去查一个小小的内存泄漏可要花不少功夫的。目前已经有很多这方面的工具,比较著名的如Rational Purify,不过Purify对Linux的支持比较少,而且有一种泄漏是由于运行过程中不断增长但是在程序结束的时候释放的泄漏这些工具有时也无能为力,另外一条路是通过重载new,delete,malloc等来达到内存监控的目的,这些方法比较简单灵活,而且仅用来检查自己的代码部分的错误,因此显得比较有效,不过也存在着自身的一些局限性,下面逐一介绍:
1、MFC's 重载操作符
这种方法的核心语句是:
#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__,__LINE)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW
但是这种方法使用了new操作符的replacement 操作,因此就要求程序中不能使用replacement操作。但是STL中广泛的使用了new操作符的replacement方法。
2、宏替换技巧篇
int __declspec( thread ) THIS_LINE;
char __declspec( thread ) THIS_FILE[1024];
#define delete (THIS_LINE=__LINE__,strcpy(THIS_FILE, __FILE__),0)? 0 : delete
#define new (THIS_LINE=__LINE__,strcpy(THIS_FILE, __FILE__),0)? 0 : new
这种方法的缺点是THIS_FILE和THIS_LINE必须使用线程局部变量,而且最郁闷的事情是它不能够覆盖直接调用operator new的场合。而且该方法不能使用于GCC编译器
3、Hook Alloc
一般的程序内存分配都是通过CRT调用的,而且LINUX的和WINDOW的CRT实现都支持malloc hook.对于Windows程序是:_CrtSetAllocHook具体用法可以参考MSDN。而且Windows下面的这个HOOK无法获得发分配的内存地址,但是每次都有一个requestID,而且这个HOOK是同时支持alloc,malloc,realloc,delete的,删除的时候和分配的时候使用的是同一个requestID。当发现有内存泄漏的时候还需要通过alloc的时候保存的THREAD context才能够通过使用dbghelp.dll来dump出当时的代码位置。不过这个方法最大的好处是不但但连malloc,new而且你的程序就算调用了别的模块的open函数,里面调用了malloc,而你没有使用close这样的内存泄漏也能检查出来,的确非常好用。不过这种方法的致命的缺陷是对于静态对象无能为力,因为既然是hook总是需要在一个时间调用,如果在此之前就已经分配的内存是无法检测出来了。
Linux下面的hook函数是:__malloc_hook等5个函数,另外linux下面需要使用gdb去dunp出代码行,这个和windows下面不一样。windows下面的实现参考StackWalker这个工具,做得非常不错。而Linux下面则是gcc自带的mtrace,用法如下:
如果你更想读生(raw)的原始文档, 请参考glibc info的"Allocation Debugging"一章 (执行info libc); otherwise, you are with me.
glibc提供了一个检查内存泄漏的方法, 前提是你的程序使用glibc的标准函数分配内存(如malloc, alloc...):
1. 在需要内存泄漏检查的代码的开始调用void mtrace(void) (在mcheck.h中有声明). mtrace为malloc等函数安装hook, 用于记录内存分配信息.在需要内存泄漏检查的代码的结束调用void muntrace(void).
注意: 一般情况下不要调用muntrace, 而让程序自然结束. 因为可能有些释放内存代码要到muntrace之后才运行.
2. 用debug模式编译被检查代码(-g或-ggdb)
3. 设置环境变量MALLOC_TRACE为一文件名, 这一文件将存有内存分配信息.
4. 运行被检查程序, 直至结束或muntrace被调用.
5. 用mtrace命令解析内存分配Log文件($MALLOC_TRACE)
(mtrace foo $MALLOC_TRACE, where foo is the executible name) 如果有内存泄漏, mtrace会输出分配泄漏内存的代码位置,以及分配数量.
可惜的是目前的mtrace只能解析GCC编译出来的行数,如果是G++编译出来的它就不行了……