黑客风云——风云网络
设为首页 加入收藏 我要投稿 网站地图
您现在的位置: 黑客风云 >> 黑客文章 >> 操作系统 >> LINIX UNIX >> 文章正文
[图文]如何在Linux操作系统下检测内存泄漏
      ★★★
如何在Linux操作系统下检测内存泄漏
文章整理发布:黑客风云 文章来源:www.05112.com 更新时间:2006-9-27

  4.错误方式删除带来的问题

  以上我们已经构建了一个具备基本内存泄漏检测功能的子系统,下面让我们来看一下关于内存泄漏方面的一些稍微高级一点的话题。

  首先,在我们编制 c++ 应用时,有时需要在堆上创建单个对象,有时则需要创建对象的数组。关于 new 和 delete 原理的叙述我们可以知道,对于单个对象和对象数组来说,内存分配和删除的动作是大不相同的,我们应该总是正确的使用彼此搭配的 new 和 delete 形式。但是在某些情况下,我们很容易犯错误,比如如下代码:

class Test {}; 
…… 
Test* pAry = new Test[10];//创建了一个拥有 10 个 Test 对象的数组 
Test* pObj = new Test;//创建了一个单对象 
…… 
delete []pObj;//本应使用单对象形式 delete pObj 进行内存释放, 
却错误的使用了数 
//组形式 
delete pAry;//本应使用数组形式 delete []pAry  
进行内存释放,却错误的使用了单对 
//象的形式

  不匹配的 new 和 delete 会导致什么问题呢?C++ 标准对此的解答是"未定义",就是说没有人向你保证会发生什么,但是有一点可以肯定:大多不是好事情--在某些编译器形成的代码中,程序可能会崩溃,而另外一些编译器形成的代码中,程序运行可能毫无问题,但是可能导致内存泄漏。

  既然知道形式不匹配的 new 和 delete 会带来的问题,我们就需要对这种现象进行毫不留情的揭露,毕竟我们重载了所有形式的内存操作 operator new,operator new[],operator delete,operator delete[]。

  我们首先想到的是,当用户调用特定方式(单对象或者数组方式)的 operator new 来分配内存时,我们可以在指向该内存的指针相关的数据结构中,增加一项用于描述其分配方式。当用户调用不同形式的 operator delete 的时候,我们在 map 中找到与该指针相对应的数据结构,然后比较分配方式和释放方式是否匹配,匹配则在 map 中正常删除该数据结构,不匹配则将该数据结构转移到一个所谓 "ErrorDelete" 的 list 中,在程序最终退出的时候和内存泄漏信息一起打印。

  上面这种方法是最顺理成章的,但是在实际应用中效果却不好。原因有两个,第一个原因我们上面已经提到了:当 new 和 delete 形式不匹配时,其结果"未定义"。如果我们运气实在太差--程序在执行不匹配的 delete 时崩溃了,我们的全局对象(appMemory)中存储的数据也将不复存在,不会打印出任何信息。第二个原因与编译器相关,前面提到过,当编译器处理自定义数据类型或者自定义数据类型数组的 new 和 delete 操作符的时候,通常使用编译器相关的 cookie 技术。这种 cookie 技术在编译器中可能的实现方式是:new operator 先计算容纳所有对象所需的内存大小,而后再加上它为记录 cookie 所需要的内存量,再将总容量传给operator new 进行内存分配。当 operator new 返回所需的内存块后,new operator 将在调用相应次数的构造函数初始化有效数据的同时,记录 cookie 信息。而后将指向有效数据的指针返回给用户。也就是说我们重载的 operator new 所申请到并记录下来的指针与 new operator 返回给调用者的指针不一定一致(图3)。当调用者将 new operator 返回的指针传给 delete operator 进行内存释放时,如果其调用形式相匹配,则相应形式的 delete operator 会作出相反的处理,即调用相应次数的析构函数,再通过指向有效数据的指针位置找出包含 cookie 的整块内存地址,并将其传给 operator delete 释放内存。如果调用形式不匹配,delete operator 就不会做上述运算,而直接将指向有效数据的指针(而不是真正指向整块内存的指针)传入 operator delete。因为我们在 operator new 中记录的是我们所分配的整块内存的指针,而现在传入 operator delete 的却不是,所以就无法在全局对象(appMemory)所记录的数据中找到相应的内存分配信息。

  综上所述,当 new 和 delete 的调用形式不匹配时,由于程序有可能崩溃或者内存子系统找不到相应的内存分配信息,在程序最终打印出 "ErrorDelete" 的方式只能检测到某些"幸运"的不匹配现象。但我们总得做点儿什么,不能让这种危害极大的错误从我们眼前溜走,既然不能秋后算帐,我们就实时输出一个 warning 信息来提醒用户。什么时候抛出一个 warning 呢?很简单,当我们发现在 operator delete 或 operator delete[] 被调用的时候,我们无法在全局对象(appMemory)的 map 中找到与传入的指针值相对应的内存分配信息,我们就认为应该提醒用户。

  既然决定要输出warning信息,那么现在的问题就是:我们如何描述我们的warning信息才能更便于用户定位到不匹配删除错误呢?答案:在 warning 信息中打印本次 delete 调用的文件名和行号信息。这可有点困难了,因为对于 operator delete 我们不能向对象 operator new 一样做出一个带附加信息的重载版本,我们只能在保持其接口原貌的情况下,重新定义其实现,所以我们的 operator delete 中能够得到的输入只有指针值。在 new/delete 调用形式不匹配的情况下,我们很有可能无法在全局对象(appMemory)的 map 中找到原来的 new 调用的分配信息。怎么办呢?万不得已,只好使用全局变量了。我们在检测子系统的实现文件中定义了两个全局变量(DELETE_FILE, DELETE_LINE)记录 operator delete 被调用时的文件名和行号,同时为了保证并发的 delete 操作对这两个变量访问同步,还使用了一个 mutex(至于为什么是 CCommonMutex 而不是一个 pthread_mutex_t,在"实现上的问题"一节会详细论述,在这里它的作用就是一个 mutex)。

char DELETE_FILE[ FILENAME_LENGTH ] = {0}; 
int DELETE_LINE = 0; 
CCommonMutex globalLock;

  而后,在我们的检测子系统的头文件中定义了如下形式的 DEBUG_DELETE:

extern char DELETE_FILE[ FILENAME_LENGTH ]; 
extern int DELETE_LINE; 
extern CCommonMutex globalLock;//在后面解释 
#define DEBUG_DELETE globalLock.Lock();  
if (DELETE_LINE != 0) BuildStack(); (//见第六节解释) 
strncpy( DELETE_FILE, __FILE__,FILENAME_LENGTH - 1 ); 
DELETE_FILE[ FILENAME_LENGTH - 1 ]= '';  
DELETE_LINE = __LINE__;  
delete

  在用户被检测文件中原来的宏定义中添加一条:

#include "MemRecord.h" 
#if defined( MEM_DEBUG ) 
#define new DEBUG_NEW 
#define delete DEBUG_DELETE 
#endif

  这样,在用户被检测文件调用 delete operator 之前,将先获得互斥锁,然后使用调用点文件名和行号对相应的全局变量(DELETE_FILE,DELETE_LINE)进行赋值,而后调用 delete operator。当 delete operator 最终调用我们定义的 operator delete 的时候,在获得此次调用的文件名和行号信息后,对文件名和行号全局变量(DELETE_FILE,DELETE_LINE)重新初始化并打开互斥锁,让下一个挂在互斥锁上的 delete operator 得以执行。

  在对 delete operator 作出如上修改以后,当我们发现无法经由 delete operator 传入的指针找到对应的内存分配信息的时候,就打印包括该次调用的文件名和行号的 warning。

上一页  [1] [2] [3] [4] [5] 下一页  

文章录入:sygbox    责任编辑:sygbox 
【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
VIP 专 区
Copyright @2006 黑客风云 ●业务联系:QQ 联系怪人 联系奇人 Email:给怪人发邮件 给奇人发邮件
ICP备案:冀06009886