概述
内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,在大型的、复杂的应用程序中,内存泄漏是常见的问题。当以前分配的一片内存不再需要使用或无法访问时,但是却并没有释放它,这时就出现了内存泄漏。尽管优秀的编程实践可以确保最少的泄漏,但是根据经验,当使用大量的函数对相同的内存块进行处理时,很可能会出现内存泄漏。

内存泄露可以分为以下几类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅一块内存发生泄漏。比如,在一个Singleton类的构造函数中分配内存,在析构函数中却没有释放该内存。而Singleton类只存在一个实例,所以内存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 (百度百科)

检测工具
现在有很多方法来检测内存泄露,以下列举了常用的内存泄露检测工具。

Visual Leak Detecter
应用环境:Windows + VC

编程语言:C/C++

使用方法:只需包含头文件vld.h,并添加提供的lib

结果输出:输出到VC的调试窗口中

设计思路: 注册_CrtSetAllocHook钩子函数,使用VC自带的CRT Debug Heap

优缺点:可以获得内存泄露点的调用堆栈,可以得到内存泄露的完整数据

如何获取:http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio

Bounds Checker
应用环境:Windows + VC6.0

编程语言:C/C++

使用方法:安装使用,会自动在VC内创建右键菜单

结果输出:输出到VC的调试窗口中

设计思路: 未知

优缺点:可以检测内存泄露;资源泄漏;对指针的错误操作,内存读、写溢出;使用未初始化的内存

如何获取:http://3ddown.com/soft/31594.htm,安装licence时,需要将日期调整为2008年,然后安装licence。有一个licence安装后显示是8.3的,但是可以使用。

mtrace
应用环境:Linux GLIBC

编程语言:C

使用方法: 包含头文件mcheck.h,定义环境变量MALLOC_TRACE为输出文件名,程序开始时调用mtrace()即可。

结果输出:用户指定的文件

设计思路: 为malloc,realloc,free函数添加钩子函数,记录每一对malloc-free的执行

优缺点:只能检查使用malloc/realloc/free造成的的内存泄露

如何获取:GLIBC自带,可直接使用

memwatch
应用环境:Linux

编程语言:C

使用方法: 加入memwatch.h,编译时加上-DMEMWATCH -DMW_STDIO及memwatch.c

结果输出:输出文件名称为memwatch.log,在程序执行期间,错误提示都会显示在stdout上

设计思路:将malloc/realloc/calloc/strdup/free等重定义为mwMalloc(sz, FILE, LINE)等,内部维护一个操作链表

优缺点:能检测双重释放(double-free)、错误释放(erroneous free)、内存泄漏(unfreed memory)、溢出(Overflow)、下溢(Underflow)等等

如何获取:http://memwatch.sourceforge.net/

valgrind
应用环境:Linux

编程语言:C/C++

使用方法: 加入memwatch.h,编译时加上-DMEMWATCH -DMW_STDIO及memwatch.c

结果输出:输出文件名称为memwatch.log,在程序执行期间,错误提示都会显示在stdout上

设计思路:根据软件的内存操作维护一个有效地址空间表和无效地址空间表(进程的地址空间)

优缺点:能够检测:

使用未初始化的内存 (Use of uninitialised memory)
使用已经释放了的内存 (Reading/writing memory after it has been free’d)
使用超过 malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)
对堆栈的非法访问 (Reading/writing inappropriate areas on the stack)
申请的空间是否有释放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
如何获取:http://valgrind.org/

debug_new
应用环境:Linux/Windows

编程语言:C++

使用方法: 包含头文件debug_new.h,链接debug_new.cpp

结果输出:控制台console

设计思路: 通过重载new和delete操作符来捕获内存申请/释放请求,并在程序内部维护一个全局静态变量的哈希链表。在new操作符中,不仅仅分配用户所要求的内存,而是在为每次分配的内存都添加一个头部,存储着此次分配的位置信息和链表指针,new返回的是分配的这块内存加上头部偏移后的值,而在之前已经将此返回值作了HASH计算并添加到HASH链表中了。delete的时候先根据要释放的指针地址做HASH计算,然后再遍历数组HASH值处的链表进行查找,如果找到则将该节点移除,未找到就abort。这样在程序结束之后,通过检查此数组中是否还有未释放的内存块来确定是否有内存泄露。

优缺点:跨平台,仅用于C++程序,

如何获取:http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html

总结
以上的这些分析工具,所使用的方法大致分为以下几种:

1、注册内存分配/释放钩子函数(hook)。在Linux下可以malloc_hook, free_hook等5个钩子函数,在Windows下可以注册_CrtSetAllocHook钩子函数,这样在分配内存的时候就可以捕获这一请求并加以处理。Visual Leak Detecter和mtrace使用此方式。

2、使用宏定义替换。将用户代码中的malloc, free 替换为宏定义的 mwMalloc(sz, FILE, LINE)等自定义函数,从而跟踪内存请求,memwatch即使用此方式。

3、操作符重载。此方法仅用于C++语言中,通过重载new、delete操作符来实现跟踪内存请求,重载后的操作符类似于钩子函数意义。debug_new采用此方式。

这些工具的输出方式也分以下几种:

1、Windows VC环境下一般输出到调试窗口中,因此VC本身就提供了一个理想的输出场所,并且GUI应用程序输出到标准输出时不可见的。Visual Leak Detecter采用此法。

2、输出到标准输出或标准错误输出:控制台应用程序可以输出到屏幕,如memwatch, valgrind, debug_new都是采用这种方法。

3、输出到日志文件:将结果输出到用户指定或默认的日志文件中,如mtrace和memwatch。

此外,这些工具的内存检测方式无非也分为两种:

1、维护一个内存操作链表,当有内存申请操作时,将其加入此链表中,当有释放操作时,从申请操作从链表中移除。如果到程序结束后此链表中还有内容,说明有内存泄露了;如果要释放的内存操作没有在链表中找到对应操作,则说明是释放了多次。使用此方法的有VC内置的调试工具,Visual Leak Detecter,mtrace, memwatch, debug_new。

2、模拟进程的地址空间。仿照操作系统对进程内存操作的处理,在用户态下维护一个地址空间映射,此方法要求对进程地址空间的处理有较深的理解。因为Windows的进程地址空间分布不是开源的,所以模拟起来很困难,因此只支持Linux。采用此方法的是valgrind。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐