如何在linux下检测内存泄漏

  • 格式:doc
  • 大小:161.00 KB
  • 文档页数:10

下载文档原格式

  / 10
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

如何在linux下检测内存泄漏

简介:本文针对linux下的C++程序的内存泄漏的检测方法及其实现进行探讨。其中包括C++中的new和delete的基本原理,内存检测子系统的实现原理和具体方法,以及内存泄漏检测的高级话题。作为内存检测子系统实现的一部分,提供了一个具有更好的使用特性的互斥体(Mutex)类。

1.开发背景

在windows下使用VC编程时,我们通常需要DEBUG模式下运行程序,而后调试器将在退出程序时,打印出程序运行过程中在堆上分配而没有释放的内存信息,其中包括代码文件名、行号以及内存大小。该功能是MFC Framework提供的内置机制,封装在其类结构体系内部。

在linux或者unix下,我们的C++程序缺乏相应的手段来检测内存信息,而只能使用top指令观察进程的动态内存总额。而且程序退出时,我们无法获知任何内存泄漏信息。为了更好的辅助在linux下程序开发,我们在我们的类库项目中设计并实现了一个内存检测子系统。下文将简述C++中的new和delete的基本原理,并讲述了内存检测子系统的实现原理、实现中的技巧,并对内存泄漏检测的高级话题进行了讨论。

2.New和delete的原理

当我们在程序中写下new和delete时,我们实际上调用的是C++语言内置的new operator和delete operator。所谓语言内置就是说我们不能更改其含义,它的功能总是一致的。以new operator为例,它总是先分配足够的内存,而后再调用相应的类型的构造函数初始化该内存。而delete operator总是先调用该类型的析构函数,而后释放内存(图1)。我们能够施加影响力的事实上就是new operator和delete operator 执行过程中分配和释放内存的方法。

new operator为分配内存所调用的函数名字是operator new,其通常的形式是void * operator

new(size_t size);其返回值类型是void*,因为这个函数返回一个未经处理(raw)的指针,未初始化的内存。参数size确定分配多少内存,你能增加额外的参数重载函数operator new,但是第一个参数类型必须是size_t。

delete operator为释放内存所调用的函数名字是operator delete,其通常的形式是void operator delete(void *memoryToBeDeallocated);它释放传入的参数所指向的一片内存区。

这里有一个问题,就是当我们调用new operator分配内存时,有一个size参数表明需要分配多大的内存。但是当调用delete operator时,却没有类似的参数,那么delete operator如何能够知道需要释放该指针指向的内存块的大小呢?答案是:对于系统自有的数据类型,语言本身就能区分内存块的大小,而对于自定义数据类型(如我们自定义的类),则operator new和operator delete之间需要互相传递信息。

当我们使用operator new为一个自定义类型对象分配内存时,实际上我们得到的内存要比实际对象的内存大一些,这些内存除了要存储对象数据外,还需要记录这片内存的大小,此方法称为cookie。这一点上的实现依据不同的编译器不同。(例如MFC选择在所分配内存的头部存储对象实际数据,而后面的部分存储边界标志和内存大小信息。g++则采用在所分配内存的头4个自己存储相关信息,而后面的内存存储对象实际数据。)当我们使用delete operator进行内存释放操作时,delete operator就可以根据这些信息正确的释放指针所指向的内存块。

以上论述的是对于单个对象的内存分配/释放,当我们为数组分配/释放内存时,虽然我们仍然使用new operator和delete operator,但是其内部行为却有不同:new operator调用了operator new的数组版的兄弟-operator new[],而后针对每一个数组成员调用构造函数。而delete operator先对每一个数组成员调用析构函数,而后调用operator delete[]来释放内存。需要注意的是,当我们创建或释放由自定义数据类型所

构成的数组时,编译器为了能够标识出在operator delete[]中所需释放的内存块的大小,也使用了编译器相关的cookie技术。

综上所述,如果我们想检测内存泄漏,就必须对程序中的内存分配和释放情况进行记录和分析,也就是说我们需要重载operator new/operator new[];operator delete/operator delete[]四个全局函数,以截获我们所需检验的内存操作信息。

3.内存检测的基本实现原理

上文提到要想检测内存泄漏,就必须对程序中的内存分配和释放情况进行记录,所能够采取的办法就是重载所有形式的operator new和operator delete,截获new operator和delete operator执行过程中的内存操作信息。下面列出的就是重载形式

void* operator new( size_t nSize, char* pszFileName, int nLineNum )

void* operator new[]( size_t nSize, char* pszFileName, int nLineNum )

void operator delete( void *ptr )

void operator delete[]( void *ptr )

我们为operator new定义了一个新的版本,除了必须的size_t nSize参数外,还增加了文件名和行号,这里的文件名和行号就是这次new operator操作符被调用时所在的文件名和行号,这个信息将在发现内存泄漏时输出,以帮助用户定位泄漏具体位置。对于operator delete,因为无法为之定义新的版本,我们直接覆盖了全局的operator delete的两个版本。

在重载的operator new函数版本中,我们将调用全局的operator new的相应的版本并将相应的size_t 参数传入,而后,我们将全局operator new 返回的指针值以及该次分配所在的文件名和行号信息记录下来,这里所采用的数据结构是一个STL 的map,以指针值为key 值。当operator delete 被调用时,如果调用方式正确的话(调用方式不正确的情况将在后面详细描述),我们就能以传入的指针值在map 中找到相应的数据项并将之删除,而后调用free 将指针所指向的内存块释放。当程序退出的时候,map 中的剩余的数据项就是我们企图检测的内存泄漏信息--已经在堆上分配但是尚未释放的分配信息。

以上就是内存检测实现的基本原理,现在还有两个基本问题没有解决:

1) 如何取得内存分配代码所在的文件名和行号,并让new operator 将之传递给我们重载的operator new。

2) 我们何时创建用于存储内存数据的map 数据结构,如何管理,何时打印内存泄漏信息。

先解决问题1。首先我们可以利用 C 的预编译宏__FILE__ 和__LINE__,这两个宏将在编译时在指定位置展开为该文件的文件名和该行的行号。而后我们需要将缺省的全局new operator 替换为我们自定义的能够传入文件名和行号的版本,我们在子系统头文件MemRecord.h 中定义:

#define DEBUG_NEW new(__FILE__, __LINE__ )

而后在所有需要使用内存检测的客户程序的所有的cpp 文件的开头加入

#include "MemRecord.h"

#define new DEBUG_NEW

就可以将客户源文件中的对于全局缺省的new operator 的调用替换为new (__FILE__,__LINE__) 调用,而该形式的new operator将调用我们的operator new (size_t nSize, char* pszFileName, int nLineNum),其中nSize 是由new operator 计算并传入的,而new 调用点的文件名和行号是由我们自定义版本的new operator 传入的。我们建议在所有用户自己的源代码文件中都加入上述宏,如果有的文件中使用内存检测子系统而有的没有,则子系统将可能因无法监控整个系统而输出一些泄漏警告。

再说第二个问题。我们用于管理客户信息的这个map 必须在客户程序第一次调用new operator 或者delete operator 之前被创建,而且在最后一个new operator 和delete operator 调用之后进行泄漏信