6 对于operator new的内存泄露检测

参看基础第一节,我们可以做一个比较简易的内存泄露捕捉器.其原理就是重载全局的operator new 和全局的operator delete,然后在重载中做一些小小的手段即可

6.1 MemLeak.h

代码如下:<文件在Fade/MemLeak/MemLeak.h>

/************************************************************************
Author:Tanglx
Date:2017/11/02
FileName:MemLeak.h
Describe:注意如下问题
1.该头文件不可用于placement new
2.请将该文件在STL库以及其他使用了placement new的库之后包含
3.该头文件无法用于有自定义operator new 或者operator delete的类,编译时候可能就会报错
4.所有需要捕获内存泄露的文件都应该直接或者间接的引用该头文件
5.该头文件只能对用于全局的new所分配的内存进行泄漏检查
************************************************************************/
#ifndef _FADE_MEMLEAK_
#define _FADE_MEMLEAK_

#define MEMLEAK_SIZE (1024)
#include <Windows.h>

//内存泄露捕捉器
interface IMemLeakHandler{
	virtual void MemLeaked(void* pData,size_t size,const char* filename,int line){
		//DoNothing
	};
	template<class _ty>static _ty* CreateAutoPtr(){
		return new _ty;
	}
	static void SetHandler(IMemLeakHandler* pHandler);

	//重载下面的new和delete是为了防止Handle的new和delete进入统计
	static void* operator new(size_t size){
		return ::operator new(size);
	}
	static void* operator new(size_t size,const char* filename,int line){
		return ::operator new(size);
	}
	static void operator delete(void* pData,size_t size){
		::delete pData;
	}
};

namespace{
	struct MemInfo{
		void* pData;	//地址
		size_t size;	//大小
		char filename[MAX_PATH];	//代码所在文件
		int line;		//行数
		bool is_release;//是否被释放
		MemInfo(){
			memset(this,0,sizeof(MemInfo));
		}
	};
	MemInfo g_MemLeakList[MEMLEAK_SIZE];
	int g_MemLeakPt=0;
	IMemLeakHandler* g_pHandler=NULL;

	struct xStruct{
		//使用了结构体的构造和析构的特性
		//由于该对象必须在上述对象和变量之前调用析构,所以该对象必须在unnamed space的最下面
		xStruct(){
			IMemLeakHandler* pHandler=new IMemLeakHandler;
			IMemLeakHandler::SetHandler(pHandler);
		}
		~xStruct(){
			for (int i=0;i<g_MemLeakPt;i++){
				MemInfo* pMi=&g_MemLeakList[i];
				if (!pMi->is_release)
				{
					if (g_pHandler)
						g_pHandler->MemLeaked(pMi->pData,pMi->size,pMi->filename,pMi->line);
				}
			}
			delete g_pHandler;
			g_pHandler=NULL;
		}
	} Instance;
}
void IMemLeakHandler::SetHandler( IMemLeakHandler* pHandler )
{
	if (g_pHandler)
		delete g_pHandler;
	g_pHandler=pHandler;
}
static void* operator new(size_t size,const char* filename,int line){
	void* pData=operator new(size);
	MemInfo* pMi=&g_MemLeakList[g_MemLeakPt++];
	pMi->pData=pData;
	strncpy_s(pMi->filename,filename,sizeof(pMi->filename));
	pMi->line=line;
	pMi->size=size;
	pMi->is_release=false;
	return pData;
}
#define DBGNEW new(__FILE__,__LINE__)
#define new DBGNEW
static void operator delete(void* pData){
	for (int i=0;i<=g_MemLeakPt;i++){
		if (pData==g_MemLeakList[i].pData){
			g_MemLeakList[i].is_release=true;
		}
	}
	free(pData);
}
static void operator delete[](void* pData){
	for (int i=0;i<=g_MemLeakPt;i++){
		if (pData==g_MemLeakList[i].pData){
			g_MemLeakList[i].is_release=true;
		}
	}
	free(pData);
}
#endif

上述代码可以分为四个部分.

1.IMemLeakHandler

提供了一个接口(MemLeaked),用于获取和处理内存泄露信息.

提供了一个用于创建处理器的静态模板函数(CreateAutoPtr),用于创建我们自己的处理器

提供了一个用于设置处理器的静态函数(SetHandler),用于设置我们创建的处理器

提供了class-name newdelete,用于屏蔽捕捉

2.unnamed namespace中提供的结构体和全局变量

结构体为MemInfo,用于存储内存分配信息

g_MemLeakList,为一个用于存储所有内存分配信息的数组

g_MemLeakPt,为当前索引

g_pHandler,为全局处理器,初始化为NULL

3.xStruct

这里的xStruct是一个空结构体,但是其还是定义了一个全局变量Instance.

所以这里在程序启动时候,会构造Instance于是xStruct构造函数会被调用,而我们再构造函数中设置了默认的处理器.

而在程序退出的时候,会析构Instance,于是xStruct的析构函数会被调用,而析构函数中我们对内存泄露信息进行处理.

注意,由于处理泄露信息的时候,需要使用到第2部分中提到的这些全局变量,所以,这个Instance应该要放到unnamed namespace的最下面,保证在xStruct析构函数里面的那些全局变量还没有被释放.

4.对于global operator newdelete的重载

这部分没什么好说的,就是在new的时候保存了一些信息到g_MemLeakList,而在delete的时候去这其中修改一下信息.

如果对new的重载这里有不懂的可以去参看<基础1.1.3>节中的描述,这里的new就是根据<基础1.1.3>节中修改而来.

6.2 使用内存泄露处理器

<6.1>节中描述的内存处理器也是可以直接使用的,但是其中太多的细节暴露在外了.而且其设计的初衷也不是为了直接使用.

所以,如果要使用内存泄露处理器,我们应当自定义处理器,Fade代码中我提供了一个例子来演示如何自定义并且使用内存泄露处理器.当然你也可以直接使用它,或者对它进行修改使用

6.2.1 自定义的处理器

代码如下:<Fade/MemLeak/TraceMemLeak.h>

/************************************************************************
Author:Tanglx
Date:2017/11/03
FileName:TraceMemLeak.h
Describe:这是一个例子,用于捕获内存泄漏
************************************************************************/
#ifndef _FADE_TRACEMEMLEAK_
#define _FADE_TRACEMEMLEAK_
#include "stdio.h"
#include "MemLeak.h"
struct MyMemLeakHandler:public IMemLeakHandler{
	virtual void MemLeaked(void* pData,size_t size,const char* filename,int line){
		//这里是打印,可以更改为写日志文件或者什么的
		printf("Addr:%p,Size:%d\r\nFile:%s,Line:%d\r\n",pData,size,filename,line);
	};
};
#endif

如上代码所示,你可以根据需要修改MemLeaked接口中的处理过程.

6.2.2 自定义处理器的调用

调用的时候,我们只需要在程序需要检测的地方添加两行代码既可,一行创建处理器,一行设置处理器

如下例子所示:

#include "Fade/MemLeak/TraceMemLeak.h"
int main()
{
	//创建自定义处理器
	MyMemLeakHandler* pHandler=IMemLeakHandler::CreateAutoPtr<MyMemLeakHandler>();
	//设置处理器
	MyMemLeakHandler::SetHandler(pHandler);

	int* p=new int;
	int* arr=new int[100];
	return 0;
}

运行上述代码,可以得到下列结果

Addr:00171AE0,Size:4

File:g:\fade\fade\main.cpp,Line:9

Addr:00171B40,Size:400

File:g:\fade\fade\main.cpp,Line:10

请按任意键继续. . .

6.3 小结

该代码也不是万能了,在看文档和代码的时候,也看一下MemLeak.h头部写的注意事项.

我们这里再说一遍,以示警戒.

1.该头文件不可用于placement new

2.请将该文件在STL库以及其他使用了placement new的库之后包含

3.该头文件无法用于有自定义operator new 或者 operator delete的类,编译时候可能就会报错

4.所有需要捕获内存泄露的文件都应该直接或者间接的引用该头文件

5.该头文件只能对用于全局的new所分配的内存进行泄漏检查

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值