灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现。然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行。
下面的代码是vld工具的实现,首先说一下内存检测的思路:
首先自己实现new或new[]时,并不是开辟用户所要求的空间大小,而是开辟一个节点,该节点大小为一个MemInfoBlock结构的大小 + 用户实际需要的大小。 开辟成功之后,把该节点链接到Hash()函数所映射到pHashTable数组的某一个下标之下; 当释放某个指针时,从相应的pHashTable下标下释放保存该地址记录的节点,从而完成内存的释放。
下面这幅图是整个程序对申请内存的管理模式:
下面是vld.h文件的实现
<span style="font-size:18px;">#pragma once #include<stdio.h> #include<memory> #include<stdlib.h> #define P 10 // 哈希表默认表长 #define SizeType size_t // 值类型 typedef struct MemInfoBlock { int size; char *file; int line; struct MemInfoBlock *link; }MemInfoBlock; void check_vld(); //先声明函数 class CheckMem { public: CheckMem(MemInfoBlock *ptr = NULL) { for(int i = 0; i<P; ++i) pHashTable[i] = ptr; //初始化数组 } ~CheckMem() //保证在该类对象_AfxMem最后析构的时候调用check_vld() { check_vld(); } MemInfoBlock *getpHashTable(int i) //为了维护pHashTable数组 {return pHashTable[i];} void setpHashTable(int i, MemInfoBlock* p) //为了维护pHashTable数组 {pHashTable[i] = p;} private: MemInfoBlock* pHashTable[P]; //这里没有必要显式定义哈希表,该类的私有成员就是长度为P的哈希表,元素类型为MemInfoBlock* }; //////////////////////////////////////////////////////////////////// CheckMem _AfxMem; //该对象的私有成员是一个哈希表,用来管理申请记录 // 注意,这里把哈希表封装成一个类的私有成员,是为了用这个类生成一个对象, //该对象在主函数运行之前已经构造完毕,也就是说会在所有对象都析构之后才会析构, //这样可以检查对象内部是不是开辟了空间没有释放导致内存泄露情况的发生 int Hash(SizeType x) //返回申请的节点放进_AfxMem的成员pHashTable的哪一个位置 { return x % P;} //以所申请内存长度为衡量 //////////////////////////////////////////////////////////////////// void check_vld() { int flag = 0; int count = 0; for(int i = 0; i<P; ++i) //依次遍历_AfxMem的pHashTable(实现哈希表功能)是否为空 { if(_AfxMem.getpHashTable(i) == NULL) //如果_AfxMem的成员pHashTable中下标为i的节点为空,说明下面没有内存申请记录,则检查下一个元素 continue; else //有节点不空,说明还有内存申请记录,发生了内存泄露,把flag置1 { flag = 1; MemInfoBlock *p = _AfxMem.getpHashTable(i); while(p != NULL) //依次检查哪一个文件,哪一行申请的内存没有释放 { printf("At %p: %d Bytes\n",p+1,p->size); printf("file: %s, line: %d\n",p->file,p->line); p = p->link; count++; } } } if(1 == flag) printf("\nWARNING: Visual Leak Detector detected %d memory leaks!\n",count); else printf("\nNo memory lacked dectected!\n"); } //////////////////////////////////////////////////////////////////// /*重载new, 可以实现对动态开辟的对象(或变量)空间申请*/ void* operator new(size_t sz,char *filename, int line) { void * result; //用来存放返回值 int index = Hash(sz); //index得到该申请的节点应放在_AfxMem成员pHashTable的哪个一个元素下 int total_size = sz + sizeof(MemInfoBlock); //申请内存总大小 是记录节点的大小 + 所需内存大小 MemInfoBlock *p = (MemInfoBlock*)malloc(total_size); p->size = sz; //填申请记录 p->file = filename; p->line = line; p->link = NULL; if(_AfxMem.getpHashTable(index) == NULL) //数组pHashTable下标为index的值为空,说明该元素下没有申请记录 { _AfxMem.setpHashTable(index, p); //把申请记录挂到该元素下面 } else //数组pHashTable下标为index的值不空,说明该元素下有申请记录 { p->link = _AfxMem.getpHashTable(index); //头插相对简单,申请记录 头插进下标为index的元素 _AfxMem.setpHashTable(index, p); } result = p+1; //指针p加1,不多说。实际返回值是申请记录中不曾使用的那块内存 return result; } /*重载delete, 可以实现对动态开辟的对象(或变量)空间释放*/ void operator delete(void *ptr) { MemInfoBlock *_c = (MemInfoBlock*)ptr; _c -= 1; // 得到内存申请记录的起始地址 int index = Hash(_c->size); //只需要在pHashTable中下标为index的元素下面搜索有没有ptr //ptr不可能在其他下标的元素下面,因为申请记录的插入使用了Hash(sz) if(NULL == ptr || _AfxMem.getpHashTable(index)==NULL) //下标index下的值为空,不可能在该index下找到ptr,说明ptr非法 return ; MemInfoBlock *p; if(_AfxMem.getpHashTable(index)+1 == ptr) //头结点是要找的指针ptr,则头删 { p = _AfxMem.getpHashTable(index); _AfxMem.setpHashTable(index, p->link); //pHashTable中下标为index的元素指向第二个记录节点(可能为空,也可能不空) free(p); } else { p = _AfxMem.getpHashTable(index); while(p->link!=NULL && p->link+1 != ptr) //在第2个及后面记录节点中查找ptr p = p->link; if(p->link != NULL) //说明找到了ptr { MemInfoBlock *q = p->link; //删除p指针后面的一个记录节点 p->link = q->link; free(q); } else //通过映射找了一遍,但没找到,说明ptr是非法地址 abort(); } } /*重载new[], 可以实现对动态开辟的数组的空间申请*/ /*与new大同小异, 只是在new后面加了一对[],不再重复解释 */ void* operator new[](size_t sz,char *filename, int line) { void * result; int index = Hash(sz); int total_size = sz + sizeof(MemInfoBlock); MemInfoBlock *p = (MemInfoBlock*)malloc(total_size); p->size = sz; p->file = filename; p->line = line; p->link = NULL; if(_AfxMem.getpHashTable(index) == NULL) { _AfxMem.setpHashTable(index, p); } else { p->link = _AfxMem.getpHashTable(index); _AfxMem.setpHashTable(index, p); } result = p+1; return result; } /*重载delete[], 可以实现对动态开辟的数组的空间释放*/ /*与delete大同小异, 只是在delete后面加了一对[],不再重复解释 */ void operator delete[](void *ptr) { MemInfoBlock *_c = (MemInfoBlock*)ptr; _c -= 1; // 得到内存申请记录的起始地址 int index = Hash(_c->size); if(NULL == ptr || _AfxMem.getpHashTable(index)==NULL) //下标index下的值为空,不可能在该index下找到ptr,说明ptr非法 return ; MemInfoBlock *p; if(_AfxMem.getpHashTable(index)+1 == ptr) { p = _AfxMem.getpHashTable(index); _AfxMem.setpHashTable(index, p->link); free(p); } else { p = _AfxMem.getpHashTable(index); while(p->link!=NULL && p->link+1 != ptr) p = p->link; if(p->link != NULL) { MemInfoBlock *q = p->link; p->link = q->link; free(q); } else //通过映射找了一遍,但没找到,说明ptr是非法地址 abort(); } } 下面是测试程序Main.cpp <pre name="code" class="cpp">#include"Vld.h" using namespace std; #define new new(__FILE__,__LINE__) class Test { public: Test(char *p = "") { pc = new char[strlen(p)+1]; strcpy(pc, p); } ~Test() { delete pc; } private: char *pc; }; void main() { int *pi = new int(10); double *qd = new double(12.34); Test *pt = new Test; char *pc = new char[15]; delete pi; delete qd; /*delete pt; delete []pc;*/ }
好了,现在可以使用自己实现的内存检测工具了