C++ 内存泄漏检测实现
new 和 delete是运算符,可以重载操作,通过重载new 和 delete便可以实现开堆和放堆的监管。
重载new和delete写法:
new和delete重载后 使用malloc free 来进行内存分配和释放 重载之后new 和 delete依旧可以调取构造和析构函数
重载只是重写了内存分配和释放部分
//声明
void* operator new(size_t size);
void* operator new[](size_t size);
void operator delete(void* pmem);
void operator delete[](void* pmem);
//定义
void* operator new(size_t size)
{
void* p = malloc(size);
printf("分配了%d个字节的内存,地址为%p\n", size,p);
return p;
}
void* operator new[](size_t size)
{
//传入size后会自动计算分配大小
void* p = malloc(size);
printf("分配了%d个字节的内存,地址为%p\n", size, p);
return p;
}
void operator delete(void* pmem)
{
printf("释放了地址%p指向的内存\n", pmem);
free(pmem);
}
void operator delete[](void* pmem)
{
printf("释放了地址%p指向的内存\n", pmem);
free(pmem);
}
设定结构体用来表示内存段的信息以及代码所在地址
//定义一个内存信息的结构体
typedef struct stMenInfo
{
void* pMem;//指向储存内存的地址
size_t size;//内存大小
int line;//分配内存代码的行数
char filename[256];//分配内存代码所在的文件名
char funcname[256];//分配内存代码所在的函数名
}MEM_INFO,*LP_MEM_INFO;
添加map容器用来储存内存段信息,每次调用new分配内存 便将内存段信息纳入容器管理,释放容器时将内存信息从容器中进行删除,需要将这个map容器设定为全局变量,让它的生命周期尽可能的早
//设定为全局变量主要是为了让map变量的生命周期尽可能的早
static std::map<void*, LP_MEM_INFO> g_MemMap;
typedef std::map<void*, LP_MEM_INFO> MEMMAP;
typedef MEMMAP::iterator MEMMAPItr;
添加内存管理检测类,使用该类来进行内存信息模块的添加和删除,以及通过该类的析构函数检测map容器是否为空,并进行处理
class CMemMgr
{
CMemMgr() {}
CMemMgr(const CMemMgr&) = delete;
CMemMgr& operator = (const CMemMgr&) = delete;
public:
//利用析构函数检测内存是否有泄漏 并进行处理
~CMemMgr()
{
//检查map中有没有释放的内存,输出调试的信息
if (!g_MemMap.empty())
{
OutputDebugStringA("\n--------------------------------\n发现内存泄漏信息------------------------\n");
char buf[512] = {};
int count = 0;
for(auto it:g_MemMap)
{
sprintf_s(buf, "[内存泄漏警告 %03d] 文件%s,第%d行的函数%s中泄漏了%d个字节的内存。",
++count,
it.second->filename,
it.second->line,
it.second->funcname,
it.second->size
);
free(it.second->pMem);
free(it.second);
}
g_MemMap.clear();
OutputDebugStringA("\n----------------------------\
内存泄漏信息检测结束-----------------------\n");
}
}
static CMemMgr& Instance()
{
static CMemMgr instance;
return instance;
}
//将内存压入管理map
void* Push(LP_MEM_INFO pinfo)
{
if (pinfo == nullptr || pinfo->pMem == nullptr)
{
return;
}
g_MemMap[pinfo->pMem] = pinfo;
return pinfo->pMem;
}
void Pop(void* pMem)
{
if (pMem == nullptr)
{
return;
}
//查找是否是由本管理类管理的内存
MEMMAPItr itr = g_MemMap.find(pMem);
if (itr != g_MemMap.end())
{
free(pMem); //释放外部使用的堆内存
//释放的是内存信息块的内存
free(itr->second);
g_MemMap.erase(itr);
}
}
protected:
private:
};
使用__FILE__,__FUNCTION__,__LINE__宏定义方式传入当前行数,当前文件,当前函数名
#ifndef __UNUSE_MEM_CHECK__
#define __UNUSE_MEM_CHECK__
#define new new(__FILE__,__FUNCTION__,__LINE__)
#endif // !__UNUSE_MEM_CHECK__
最后修改new和delete的重载,将内存信息在分配的时候压入管理
void* operator new(size_t size, const char* filename, const char* funcname, int line)
{
/* void* p = malloc(size);
printf("分配了%d个字节的内存,地址为%p\n", size,p);
return p;*/
//创建了用于检测的内存块
LP_MEM_INFO pinfo = (LP_MEM_INFO)malloc(sizeof(MEM_INFO));
pinfo->size = size;
pinfo->line = line;
pinfo->pMem = malloc(size);
strcpy_s(pinfo->filename,filename);
strcpy_s(pinfo->funcname, funcname);
//压入内存管理类,并返回外部使用堆内存
return CMemMgr::Instance().Push(pinfo);
}
void* operator new[](size_t size, const char* filename, const char* funcname, int line)
{
////传入size后会自动计算分配大小
//void* p = malloc(size);
//printf("分配了%d个字节的内存,地址为%p\n", size, p);
//return p;
LP_MEM_INFO pinfo = (LP_MEM_INFO)malloc(sizeof(MEM_INFO));
pinfo->size = size;
pinfo->line = line;
pinfo->pMem = malloc(size);
strcpy_s(pinfo->filename, filename);
strcpy_s(pinfo->funcname, funcname);
//压入内存管理类,并返回外部使用堆内存
return CMemMgr::Instance().Push(pinfo);
}
void operator delete(void* pmem)
{
/*printf("释放了地址%p指向的内存\n", pmem);
free(pmem);*/
CMemMgr::Instance().Pop(pmem);
}
void operator delete[](void* pmem)
{
/* printf("释放了地址%p指向的内存\n", pmem);
free(pmem);*/
CMemMgr::Instance().Pop(pmem);
}
最后效果:
总结:
定义内存信息模块结构体储存内存分配大小,内存分配地址,分配内存所在函数名,分配内存所在文件名以及代码所在行数
创建了map容器管理分配的内存信息模块
实现了单例内存检测类,通过内存检测类将内存信息模块压入管理,并且在析构函数中进行内存泄漏检测 和对未释放内存进行处理
重载new 和 delete 调用内存检测类进行压入管理和释放内存,通过宏定义方式传入内存信息模块所需信息
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!