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

1. 简述

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

2. 基本原理

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


swift检测内存泄漏的工具 内存泄漏检测方法_内存泄漏



1  
  #include 
  < 
  iostream 
  > 
  
 
    2  
   
  using 
    
  namespace 
   std;
 
    3  
   
  // 
  ---------------------------------------------------------------
 
    4  
   
  // 
   内存记录
 
    5  
   
  // 
  --------------------------------------------------------------- 
  
 
    6  
   
  class 
   MemInfo {
 
    7  
   
  private 
  :
 
    8  
     
  void 
  * 
   ptr;
 
    9  
     
  const 
    
  char 
  * 
   file;
 
   10  
    unsigned  
  int 
   line;
 
   11  
    MemInfo 
  * 
   link;
 
   12  
    friend  
  class 
   MemStack;
 
   13  
  };
 
   14  
   
  // 
  ---------------------------------------------------------------
 
   15  
   
  // 
   内存记录栈 
 
   16  
   
  // 
  --------------------------------------------------------------- 
  
 
   17  
   
  class 
   MemStack {
 
   18  
   
  private 
  :
 
   19  
    MemInfo 
  * 
   head;
 
   20  
   
  public 
  :
 
   21  
    MemStack():head(NULL) { }
 
   22  
     
  ~ 
  MemStack() { 
 
   23  
      MemInfo 
  * 
   tmp;
 
   24  
       
  while 
  (head  
  != 
   NULL) {
 
   25  
        free(head 
  -> 
  ptr);  
  // 
   释放泄漏的内存  
  
 
   26  
   
        tmp  
  = 
   head 
  -> 
  link;
 
   27  
        free(head);
 
   28  
        head  
  = 
   tmp;
 
   29  
      }
 
   30  
    }
 
   31  
     
  void 
   Insert( 
  void 
  * 
   ptr,  
  const 
    
  char 
  * 
   file, unsigned  
  int 
   line) {
 
   32  
      MemInfo 
  * 
   node  
  = 
   (MemInfo 
  * 
  )malloc( 
  sizeof 
  (MemInfo));
 
   33  
      node 
  -> 
  ptr  
  = 
   ptr; node 
  -> 
  file  
  = 
   file; node 
  -> 
  line 
  = 
  line;
 
   34  
      node 
  -> 
  link  
  = 
   head; head  
  = 
   node;    
 
   35  
    }
 
   36  
     
  void 
   Delete( 
  void 
  * 
   ptr) {
 
   37  
      MemInfo 
  * 
   node  
  = 
   head;
 
   38  
      MemInfo 
  * 
   pre  
  = 
   NULL;
 
   39  
       
  while 
  (node  
  != 
   NULL  
  && 
   node 
  -> 
  ptr 
  != 
  ptr) {
 
   40  
        pre  
  = 
   node;
 
   41  
        node  
  = 
   node 
  -> 
  link;
 
   42  
      }
 
   43  
       
  if 
  (node  
  == 
   NULL)
 
   44  
        cout  
  << 
    
  " 
  删除一个没有开辟的内存 
  " 
    
  << 
   endl;
 
   45  
       
  else 
   {
 
   46  
         
  if 
  (pre  
  == 
   NULL)  
  // 
   删除的是head  
  
 
   47  
   
          head  
  = 
   node 
  -> 
  link;
 
   48  
         
  else 
   
 
   49  
          pre 
  -> 
  link  
  = 
   node 
  -> 
  link;
 
   50  
        free(node);
 
   51  
      }
 
   52  
    }
 
   53  
     
  void 
   Print() {
 
   54  
       
  if 
  (head  
  == 
   NULL) {
 
   55  
        cout  
  << 
    
  " 
  内存都释放掉了 
  " 
    
  << 
   endl; 
 
   56  
         
  return 
  ;
 
   57  
      }
 
   58  
      cout  
  << 
    
  " 
  有内存泄露出现 
  " 
    
  << 
   endl; 
 
   59  
      MemInfo 
  * 
   node  
  = 
   head;    
 
   60  
       
  while 
  (node  
  != 
   NULL) {
 
   61  
        cout  
  << 
    
  " 
  文件名:  
  " 
    
  << 
   node 
  -> 
  file  
  << 
    
  " 
   ,  
  " 
    
  << 
    
  " 
  行数:  
  " 
    
  << 
   node 
  -> 
  line  
  << 
    
  " 
   ,  
  " 
  
 
   62  
           
  << 
    
  " 
  地址:  
  " 
    
  << 
   node 
  -> 
  ptr  
  << 
   endl; 
 
   63  
        node  
  = 
   node 
  -> 
  link;
 
   64  
      }
 
   65  
    }
 
   66  
  };
 
   67  
   
  // 
  ---------------------------------------------------------------
 
   68  
   
  // 
   全局对象 mem_stack记录开辟的内存 
 
   69  
   
  // 
  --------------------------------------------------------------- 
  
 
   70  
   
  MemStack mem_stack;
 
   71  
   
  // 
  ---------------------------------------------------------------
 
   72  
   
  // 
   重载new,new[],delete,delete[] 
 
   73  
   
  // 
  --------------------------------------------------------------- 
  
 
   74  
   
  void 
  * 
    
  operator 
    
  new 
  (size_t size,  
  const 
    
  char 
  * 
   file, unsigned  
  int 
   line) {
 
   75  
     
  void 
  * 
   ptr  
  = 
   malloc(size);
 
   76  
    mem_stack.Insert(ptr, file, line);
 
   77  
     
  return 
   ptr;
 
   78  
  }
 
   79  
   
  void 
  * 
    
  operator 
    
  new 
  [](size_t size,  
  const 
    
  char 
  * 
   file, unsigned  
  int 
   line) {
 
   80  
     
  return 
    
  operator 
    
  new 
  (size, file, line);  
  // 
   不能用new  
  
 
   81  
   
  }
 
   82  
   
  void 
    
  operator 
   delete( 
  void 
  * 
   ptr) {
 
   83  
    free(ptr);
 
   84  
    mem_stack.Delete(ptr);
 
   85  
  }
 
   86  
   
  void 
    
  operator 
   delete[]( 
  void 
  * 
   ptr) {
 
   87  
     
  operator 
   delete(ptr);
 
   88  
  }
 
   89  
   
  // 
  ---------------------------------------------------------------
 
   90  
   
  // 
   使用宏将带测试代码中的new和delte替换为重载的new和delete 
 
   91  
   
  // 
  --------------------------------------------------------------- 
  
 
   92  
   
  #define 
   new new(__FILE__,__LINE__) 
  
 
   93  
   
  // 
  ---------------------------------------------------------------
 
   94  
   
  // 
   待测试代码 
 
   95  
   
  // 
  --------------------------------------------------------------- 
  
 
   96  
   
  void 
   bad_code() {
 
   97  
     
  int 
    
  * 
  p  
  = 
    
  new 
    
  int 
  ;
 
   98  
     
  char 
    
  * 
  q  
  = 
    
  new 
    
  char 
  [ 
  5 
  ];
 
   99  
    delete []q;
 
  100  
  } 
 
  101  
  
 
  102  
   
  void 
   good_code() {
 
  103  
     
  int 
    
  * 
  p  
  = 
    
  new 
    
  int 
  ;
 
  104  
     
  char 
    
  * 
  q  
  = 
    
  new 
    
  char 
  [ 
  5 
  ];
 
  105  
    delete p;
 
  106  
    delete []q;
 
  107  
  } 
 
  108  
   
  // 
  ---------------------------------------------------------------
 
  109  
   
  // 
   测试过程 
 
  110  
   
  // 
  --------------------------------------------------------------- 
  
 
  111  
   
  int 
   main() {
 
  112  
    good_code();
 
  113  
    bad_code();
 
  114  
    mem_stack.Print();
 
  115  
    system( 
  " 
  PAUSE 
  " 
  );
 
  116  
     
  return 
    
  0 
  ;
 
  117  
  }



swift检测内存泄漏的工具 内存泄漏检测方法_内存泄漏


  输出结果为:

    

swift检测内存泄漏的工具 内存泄漏检测方法_swift检测内存泄漏的工具_03


    可见97行开辟的int,没有delete掉,输出结果也显示为97行。

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. 使用思考

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