参看基础第一节,我们可以做一个比较简易的内存泄露捕捉器.其原理就是重载全局的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 new和delete,用于屏蔽捕捉
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 new和delete的重载
这部分没什么好说的,就是在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所分配的内存进行泄漏检查