靈活自由是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;*/
}
下面是程序運行的截圖:<img src="https://img-blog.csdn.net/20150901210316887?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
好了,現在可以使用自己實現的內存檢測工具了