1. 简述
内存泄漏属于资源泄漏的一种,百度百科将内存泄漏分为四种:常发性内存泄漏、偶发性内存泄漏、一次性内存泄漏和隐式内存泄漏。
常发性指:内存泄漏的代码会被多次执行到。偶发性指:内存泄漏的代码只有在特定的条件下才会执行到。一次性指:内存泄漏的代码只会被执行到一次。隐式指:程序在运行中不断的开辟内存,知道程序结束时才释放内存,本质上虽然没有内存泄漏,但是如果这个程序在连续运行很长时间,会耗尽所有内存,导致系统崩溃。
下面首先介绍内存检测的基本原理,然后给出代码样例,最后说明针对四种内存泄漏进行检测的想法。
2. 基本原理
内存泄漏就是new出来的内存没有通过delete合理的释放掉。new和delete这两个函数就是关键点。可以重载new和delete,每次new中开辟一块内存就用链表把这个内存的信息保存下来,每次用delete删除一块内存就从链表中删除这块内存的记录。
3. 代码样例
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 } 输出结果为: 可见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. 使用思考
对于常发性和一次性的内存泄漏代码,直接放入测试就好了。对于偶发性的内存泄漏代码,只要满足特定条件,那么也就转化为常发性或者一次性的内存泄漏了。 对于隐式内存泄漏,由于程序是在很长一段时间之后导致内存耗尽,我们需要长时间观察,每隔一段时间比较一下内存的使用量,如果在一个较长的时间内,内存使用量持续增加,那么可以考虑是内存泄漏。不过调试起来可能会比较麻烦,还是需要重新审视程序设计的。
6. 参考
百度百科_内存泄漏:介绍内存泄漏的基本分类。
如何检查内存泄漏-重载new和delete:十分生动的说明。
一个跨平台的C++内存泄漏检测器:十分专业化的讲解和实现。