重载new和delete来检测内存泄漏

1. 简述

    内存泄漏属于资源泄漏的一种,百度百科将内存泄漏分为四种:常发性内存泄漏、偶发性内存泄漏、一次性内存泄漏和隐式内存泄漏。
    常发性指:内存泄漏的代码会被多次执行到。偶发性指:内存泄漏的代码只有在特定的条件下才会执行到。一次性指:内存泄漏的代码只会被执行到一次。隐式指:程序在运行中不断的开辟内存,知道程序结束时才释放内存,本质上虽然没有内存泄漏,但是如果这个程序在连续运行很长时间,会耗尽所有内存,导致系统崩溃。
    下面首先介绍内存检测的基本原理,然后给出代码样例,最后说明针对四种内存泄漏进行检测的想法。

2. 基本原理

    内存泄漏就是new出来的内存没有通过delete合理的释放掉。new和delete这两个函数就是关键点。可以重载new和delete,每次new中开辟一块内存就用链表把这个内存的信息保存下来,每次用delete删除一块内存就从链表中删除这块内存的记录。
3. 代码样例

#include <iostream>
#include <stdlib.h>

using namespace std;


//---------------------------------------------------------------
// 内存记录
//---------------------------------------------------------------
class MemInfo {
private:
  void* ptr;
  const char* file;
  unsigned int line;
  MemInfo* link;
  friend class MemStack;
};


//---------------------------------------------------------------
// 内存记录栈 
//---------------------------------------------------------------
class MemStack {
private:
  MemInfo* head;
public:
  MemStack():head(NULL) { }
  ~MemStack() { 
    MemInfo* tmp;
    while(head != NULL) {
      free(head->ptr); // 释放泄漏的内存 
      tmp = head->link;
      free(head);
      head = tmp;
    }
  }
  void Insert(void* ptr, const char* file, unsigned int line) {
    MemInfo* node = (MemInfo*)malloc(sizeof(MemInfo));
    node->ptr = ptr; node->file = file; node->line=line;
    node->link = head; head = node;    
  }
  void Delete(void* ptr) {
    MemInfo* node = head;
    MemInfo* pre = NULL;
    while(node != NULL && node->ptr!=ptr) {
      pre = node;
      node = node->link;
    }
    if(node == NULL)
      cout << "删除一个没有开辟的内存" << endl;
    else {
      if(pre == NULL) // 删除的是head 
        head = node->link;
      else 
        pre->link = node->link;
      free(node);
    }
  }
  void Print() {
    if(head == NULL) {
      cout << "内存都释放掉了" << endl; 
      return;
    }
    cout << "有内存泄露出现" << endl; 
    MemInfo* node = head;    
    while(node != NULL) {
      cout << "文件名: " << node->file << " , " << "行数: " << node->line << " , "
        << "地址: " << node->ptr << endl; 
      node = node->link;
    }
  }
};


//---------------------------------------------------------------
// 全局对象 mem_stack记录开辟的内存 
//---------------------------------------------------------------
MemStack mem_stack;


//---------------------------------------------------------------
// 重载new,new[],delete,delete[] 
//---------------------------------------------------------------
void* operator new(size_t size, const char* file, unsigned int line) {
  void* ptr = malloc(size);
  mem_stack.Insert(ptr, file, line);
  return ptr;
}
void* operator new[](size_t size, const char* file, unsigned int line) {
  return operator new(size, file, line); // 不能用new 
}
void operator delete(void* ptr) {
  free(ptr);
  mem_stack.Delete(ptr);
}
void operator delete[](void* ptr) {
  operator delete(ptr);
}


//---------------------------------------------------------------
// 使用宏将带测试代码中的new和delte替换为重载的new和delete 
//---------------------------------------------------------------
#define new new(__FILE__,__LINE__)
//---------------------------------------------------------------
// 待测试代码 
//---------------------------------------------------------------
void bad_code() {
  int *p = new int;
  char *q = new char[5];
  delete []q;
} 

void good_code() {
  int *p = new int;
  char *q = new char[5];
  //delete p;
  delete []q;
} 


//---------------------------------------------------------------
// 测试过程 
//---------------------------------------------------------------
int main() {
  good_code();
  bad_code();
  mem_stack.Print();
  system("PAUSE");
  return 0;
}

输出结果为:

有内存泄露出现
文件名: new_demo.cpp , 行数: 98 , 地址: 0xb65c70
文件名: new_demo.cpp , 行数: 104 , 地址: 0xb65c20
sh: 1: PAUSE: not found

4. 代码说明

4.1 关于new的参数问题。
    对于new int,编译器会解释为new(sizeof(int)),对于new int[5],编译器会解释为new(sizeof(int)*5)。因此使用宏定义预编译后,new int就变为new (__FILE__,__LINE__) int,编译器会解释为new(sizeof(int), __FILE__,__LINE__)。

4.2 关于MemStack
    MemStack内部也是一个链表结构,注意内部实现不能使用new和delete,只能使用malloc和free来实现链表,因为待测代码中的重载new和delete中调用了MemStack的insert和delete函数,如果insert和delete函数也调用重载后的new和delete的话,会构成死循环的,所以直接使用free和malloc比较好。
    MemStack中的析构函数,会释放掉泄漏掉的内存。

5. 使用思考

    对于常发性和一次性的内存泄漏代码,直接放入测试就好了。对于偶发性的内存泄漏代码,只要满足特定条件,那么也就转化为常发性或者一次性的内存泄漏了。
    对于隐式内存泄漏,由于程序是在很长一段时间之后导致内存耗尽,我们需要长时间观察,每隔一段时间比较一下内存的使用量,如果在一个较长的时间内,内存使用量持续增加,那么可以考虑是内存泄漏。不过调试起来可能会比较麻烦,还是需要重新审视程序设计的。

6. 参考

    百度百科_内存泄漏:介绍内存泄漏的基本分类。
    http://baike.baidu.com/view/714962.htm
    如何检查内存泄漏-重载new和delete:十分生动的说明。
    http://www.cppblog.com/dawnbreak/articles/76223.html
    一个跨平台的C++内存泄漏检测器:十分专业化的讲解和实现。
    http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html

猜你喜欢

转载自blog.csdn.net/Zhanganliu/article/details/88027760
今日推荐