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

  6.实现上的问题:嵌套delete

  在"错误方式删除带来的问题"一节中,我们对 delete operator 动了个小手术--增加了两个全局变量(DELETE_FILE,DELETE_LINE)用于记录本次 delete 操作所在的文件名和行号,并且为了同步对全局变量(DELETE_FILE,DELETE_LINE)的访问,增加了一个全局的互斥锁。在一开始,我们使用的是 pthread_mutex_t,但是在测试中,我们发现 pthread_mutex_t 在本应用环境中的局限性。

  例如如下代码:

class B {…}; 
class A { 
public: 
A() {m_pB = NULL}; 
A(B* pb) {m_pB = pb;}; 
~A() 
{ 
if (m_pB != NULL) 
行号1delete m_pB;//这句最要命 
}; 
private: 
class B* m_pB; 
…… 
} 
int main() 
{ 
A* pA = new A(new B); 
…… 
行号2delete pA; 
}

  在上述代码中,main 函数中的一句 delete pA 我们称之为"嵌套删除",即我们 delete A 对象的时候,在A对象的析构执行了另一个 delete B 的动作。当用户使用我们的内存检测子系统时,delete pA 的动作应转化为以下动作:

  上全局锁,全局变量(DELETE_FILE,DELETE_LINE)赋值为文件名和行号2,delete operator A,调用~A()。

  上全局锁,全局变量(DELETE_FILE,DELETE_LINE)赋值为文件名和行号1,delete operator B调用~B(),返回~B(),调用operator delete B。记录全局变量(DELETE_FILE,DELETE_LINE)值1并清除全局变量(DELETE_FILE,DELETE_LINE)值。

  打开全局锁,返回operator delete B,返回delete operator B,返回~A(),调用 operator delete A。记录全局变量(DELETE_FILE,DELETE_LINE)值1并清除全局变量(DELETE_FILE,DELETE_LINE)值。

  打开全局锁,返回operator delete A,返回 delete operator A。

  在这一过程中,有两个技术问题,一个是 mutex 的可重入问题,一个是嵌套删除时对全局变量(DELETE_FILE,DELETE_LINE)现场保护的问题。

  所谓 mutex 的可重入问题,是指在同一个线程上下文中,连续对同一个 mutex 调用了多次 lock,然后连续调用了多次 unlock。这就是说我们的应用方式要求互斥锁有如下特性:

  1. 要求在同一个线程上下文中,能够多次持有同一个互斥体。并且只有在同一线程上下文中调用相同次数的 unlock 才能放弃对互斥体的占有。

  2. 对于不同线程上下文持有互斥体的企图,同一时间只有一个线程能够持有互斥体,并且只有在其释放互斥体之后,其他线程才能持有该互斥体。

  Pthread_mutex_t 互斥体不具有以上特性,即使在同一上下文中,第二次调用 pthread_mutex_lock 将会挂起。因此,我们必须实现出自己的互斥体。在这里我们使用 semaphore 的特性实现了一个符合上述特性描述的互斥体 CCommonMutex(源代码见附件)。

  为了支持特性 2,在这个 CCommonMutex 类中,封装了一个 semaphore,并在构造函数中令其资源值为 1,初始值为1。当调用 CCommonMutex::lock 接口时,调用 sem_wait 得到 semaphore,使信号量的资源为 0 从而让其他调用 lock 接口的线程挂起。当调用接口 CCommonMutex::unlock 时,调用 sem_post 使信号量资源恢复为 1,让其他挂起的线程中的一个持有信号量。

  同时为了支持特性 1,在这个 CCommonMutex 增加了对于当前线程 pid 的判断和当前线程访问计数。当线程第一次调用 lock 接口时,我们调用 sem_wait 的同时,记录当前的 Pid 到成员变量 m_pid,并置访问计数为 1,同一线程(m_pid == getpid())其后的多次调用将只进行计数而不挂起。当调用 unlock 接口时,如果计数不为 1,则只需递减访问计数,直到递减访问计数为 1 才进行清除 pid、调用 sem_post。(具体代码可见附件)

  嵌套删除时对全局变量(DELETE_FILE,DELETE_LINE)现场保护的问题是指,上述步骤中在 A 的析构函数中调用 delete m_pB 时,对全局变量(DELETE_FILE,DELETE_LINE)文件名和行号的赋值将覆盖主程序中调用 delete pA 时对全局变量(DELETE_FILE,DELETE_LINE)的赋值,造成了在执行 operator delete A 时,delete pA 的信息全部丢失。

  要想对这些全局信息进行现场保护,最好用的就是堆栈了,在这里我们使用了 STL 提供的 stack 容器。在 DEBUG_DELETE 宏定义中,对全局变量(DELETE_FILE,DELETE_LINE)赋值之前,我们先判断是否前面已经有人对他们赋过值了--观察行号变量是否等于 0,如果不为 0,则应该将已有的信息压栈(调用一个全局函数 BuildStack() 将当前的全局文件名和行号数据压入一个全局堆栈globalStack),而后再对全局变量(DELETE_FILE,DELETE_LINE)赋值,再调用 delete operator。而在内存子系统的全局对象(appMemory)提供的 erase 接口里面,如果判断传入的文件名和行号为 0,则说明我们所需要的数据有可能被嵌套删除覆盖了,所以需要从堆栈中弹出相应的数据进行处理。

  现在嵌套删除中的问题基本解决了,但是当嵌套删除与 "错误方式删除带来的问题"一节的最后所描述的第一和第三种异常情况同时出现的时候,由于用户的 delete 调用没有通过我们定义的 DEBUG_DELETE 宏,上述机制可能出现问题。其根本原因是我们利用stack 保留了经由我们的 DEBUG_DELETE 宏记录的 delete 信息的现场,以便在 operator delete 和全局对象(appMemory)的 erase 接口中使用,但是用户的没经过 DEBUG_DELETE 宏的 delete 操作却未曾进行压栈操作而直接调用了 operator delete,有可能将不属于这次操作的 delete 信息弹出,破坏了堆栈信息的顺序和有效性。那么,当我们因为无法找到这次及其后续的 delete 操作所对应的内存分配信息的时候,可能会打印出错误的 warning 信息。

  展望

  以上就是我们所实现的内存泄漏检测子系统的原理和技术方案,第一版的源代码在附件中,已经经过了较严格的系统测试。但是限于我们的 C++ 知识水平和编程功底,在实现过程中肯定还有没有注意到的地方甚至是缺陷,希望能够得到大家的指正,我的 email 是 hcode@21cn.com。

  在我们所实现的内存检测子系统基础上,可以继续搭建内存分配优化子系统,从而形成一个完整的内存子系统。一种内存分配优化子系统的实现方案是一次性分配大块的内存,并使用特定的数据结构管理之,当内存分配请求到来时,使用特定算法从这块大内存中划定所需的一块给用户使用,而用户使用完毕,在将其划为空闲内存。这种内存优化方式将内存分配释放转换为简单的数据处理,极大的减少了内存申请和释放所耗费的时间。

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

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