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 调用内存检测类进行压入管理和释放内存,通过宏定义方式传入内存信息模块所需信息

posted @   月入我怀  阅读(423)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示