vld 内存泄露检测工具vld的实现

9 篇文章 0 订阅
6 篇文章 0 订阅
     初识Visual Leak Detector

    灵活自由是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="" />
 

 
 

好了,现在可以使用自己实现的内存检测工具了微笑微笑



初识Visual Leak Detector   灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现。然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行。另外内存问题的一个共同特点是,内存问题本身并不会有很明显的现象,当有异常现象出现时已时过境迁,其现场已非出现问题时的现场了,这给调试内存问题带来了很大的难度。   Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。相比较其它的内存泄露检测工具,它在检测内存泄漏的同时,还具有如下特点:   1、 可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在文件及行号;   2、 可以得到泄露内存的完整数据;   3、 可以设置内存泄露报告的级别;   4、 它是一个已经打包的lib,使用时无须编译它的源代码。而对于使用者自己的代码,也只需要做很小的改动;   5、 他的源代码使用GNU许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理的读者,是一个不错的选择。   可见,从使用角度来讲,Visual Leak Detector简单易用,对于使用者自己的代码,唯一的修改是#include Visual Leak Detector的头文件后正常运行自己的程序,就可以发现内存问题。从研究的角度来讲,如果深入Visual Leak Detector源代码,可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存操作的常用技巧等。   本文首先将介绍Visual Leak Detector的使用方法与步骤,然后再和读者一起初步的研究Visual Leak Detector的源代码,去了解Visual Leak Detector的工作原理。   使用Visual Leak Detector(1.0)   下面让我们来介绍如何使用这个小巧的工具。   首先从网站上下载zip包,解压之后得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等文件。将.h文件拷贝到Visual C++的默认include目录下,将.lib文件拷贝到Visual C++的默认lib目录下,便安装完成了。因为版本问题,如果使用windows 2000或者以前的版本,需要将dbghelp.dll拷贝到你的程序的运行目录下,或其他可以引用到的目录。   接下来需要将其加入到自己的代码中。方法很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。如下是一个示例程序:   #include   void main()   {   …   }   接下来让我们来演示如何使用Visual Leak Detector检测内存泄漏。下面是一个简单的程序,用new分配了一个int大小的堆内存,并没有释放。其申请的内存地址用printf输出到屏幕上。   #include   #include   #include   void f()   {   int *p = new int(0x12345678);   printf("p=%08x, ", p);   }   void main()   {   f();   }   编译运行后,在标准输出窗口得到:   p=003a89c0   在Visual C++的Output窗口得到:   WARNING: Visual Leak Detector detected memory leaks!   ---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57号块0x003A89C0地址泄漏了4个字节   Call Stack: --下面是调用堆栈   d:\test\testvldconsole\testvldconsole\main.cpp (7): f --表示在main.cpp第7行的f()函数   d:\test\testvldconsole\testvldconsole\main.cpp (14): main –双击以引导至对应代码处   f:\rtm\vctools\crt_bld\self_x8
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值