前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >重载 new、delete 检测内存泄露

重载 new、delete 检测内存泄露

作者头像
我与梦想有个约会
发布2023-10-21 14:35:14
2140
发布2023-10-21 14:35:14
举报
文章被收录于专栏:jiajia_deng

内存泄露带来的问题我想我就不必多少了,检测内存泄露有很多种方法,比如使用一些智能指针。但本文介绍的方法有些不同,我们将自己维护一个数组列表,记录下 new 内存时代码所在的文件、行号、以及大小、和是否已经被 delete 信息,将这些信息放到我们维护的数组中,当程序要检查内存泄露或者程序退出时,我们遍历整个堆内存,并把每一个堆内存块在我们维护的数组中遍历,如果发现某些内存并没有被标记为 delete 状态,那么则判定为泄露。

代码示意图

效果图

这里的代码巧妙的用到了 Visual Studio 一个小技巧,在输出窗口中输入文件+行号后,我们可以双击这一行的内容快速定位到文件和具体行。先看一下效果图。

2016-04-15_114209
2016-04-15_114209

实现代码

代码语言:javascript
复制
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>

#define H_ALLOC(sz)        HeapAlloc(GetProcessHeap(),0,sz)
#define H_CALLOC(sz)   HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sz)
#define H_REALLOC(p,sz) HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,p,sz)
#define H_FREE(p)      HeapFree(GetProcessHeap(),0,p)

//支持memory leak 的new 和 delete 主要用于调试
#ifdef _DEBUG

typedef struct _ST_BLOCK_INFO
{
    TCHAR m_pszSourceFile[MAX_PATH];    // 执行 new 语句的源文件
    INT   m_iSourceLine;                // 执行 new 语句的行号
    BOOL  m_bDelete;                    // 是否被 DELETE
    VOID* m_pMemBlock;                  // 内存起始地址
}ST_BLOCK_INFO,*PST_BLOCK_INFO;

HANDLE         g_hBlockInfoHeap = NULL; // 用以存放管理数组的堆

UINT           g_nMaxBlockCnt = 100;    // 默认让管理 new 次数的数组有 100 个成员
UINT           g_nCurBlockIndex = 0;    // 默认起始下标 0
PST_BLOCK_INFO g_pMemBlockList = NULL;  // 管理 new 次数的数组其实地址默认为 NULL,需要为其单独创建一个堆实现动态扩容

void* __cdecl operator new(size_t nSize,LPCTSTR pszCppFile,int iLine)
{
    // 如果管理数组当前已经分配了堆内存
    if(NULL != g_pMemBlockList)
    {   
        // 比较是否超出了最大成员数
        if( g_nCurBlockIndex >= g_nMaxBlockCnt )    
        {
            //如果当前块信息超过最大值,那么就扩大数组
            g_nMaxBlockCnt *= 2;
            g_pMemBlockList = (PST_BLOCK_INFO)HeapReAlloc(g_hBlockInfoHeap,HEAP_ZERO_MEMORY,g_pMemBlockList,g_nMaxBlockCnt * sizeof(ST_BLOCK_INFO));
        }
    }
    else
    {
        // 如果还没有给管理数组创建堆
        if( NULL == g_hBlockInfoHeap )
        {
            //如果块信息堆还没有创建那么就创建,并设置为LFH
            g_hBlockInfoHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS,0,0);
            ULONG  HeapFragValue = 2;
            HeapSetInformation( g_hBlockInfoHeap,HeapCompatibilityInformation,&HeapFragValue ,sizeof(HeapFragValue) ) ;
        }
        //申请块信息数组
        g_pMemBlockList = (PST_BLOCK_INFO)HeapAlloc(g_hBlockInfoHeap,HEAP_ZERO_MEMORY,g_nMaxBlockCnt * sizeof(ST_BLOCK_INFO));
    }

    // 分配内存
    void* pRet = H_ALLOC(nSize);

    //记录当前这个分配操作的信息
    g_pMemBlockList[g_nCurBlockIndex].m_pMemBlock   = pRet;                                 // 记录起始地址
    StringCchCopy(g_pMemBlockList[g_nCurBlockIndex].m_pszSourceFile,MAX_PATH,pszCppFile);   // 记录执行 new 的文件
    g_pMemBlockList[g_nCurBlockIndex].m_iSourceLine = iLine;                                // 记录执行 new 的行号
    g_pMemBlockList[g_nCurBlockIndex].m_bDelete     = FALSE;                                // 刚申请,将 DELETE 置为 FALSE

    // 管理 new 次数的数组索引下标 ++
    ++ g_nCurBlockIndex;

    // 返回实际分配的内存地址
    return pRet;
}

void __cdecl operator delete(void* p)
{
    for( UINT i = 0;i < g_nCurBlockIndex; i ++ )
    {
        //遍历块信息数组,找到对应的块信息,打上已删除标记  
        if( p == g_pMemBlockList[i].m_pMemBlock )
        {
            g_pMemBlockList[i].m_bDelete = TRUE;
            break;
        }
    }
    H_FREE(p);
}

void __cdecl operator delete(void* p,LPCTSTR pszCppFile,int iLine)
{
    ::operator delete(p);
    H_FREE(p);
}

void GRSMemoryLeak(BOOL bDestroyHeap = FALSE)
{//内存泄露检测
    TCHAR pszOutPutInfo[2*MAX_PATH];
    BOOL  bRecord = FALSE;
    PROCESS_HEAP_ENTRY phe = {};
    HeapLock(GetProcessHeap());
    OutputDebugString(_T("开始检查内存泄露情况.........\n"));

    //遍历进程默认堆
    while (HeapWalk(GetProcessHeap(), &phe))
    {
        // 如果是已分配内存
        if( PROCESS_HEAP_ENTRY_BUSY & phe.wFlags )
        {
            bRecord = FALSE;
            // 查找我们维护的数组中这个块是否被标记为 delete
            for(UINT i = 0; i < g_nCurBlockIndex; i ++ )
            {
                if( phe.lpData == g_pMemBlockList[i].m_pMemBlock )
                {
                    if(!g_pMemBlockList[i].m_bDelete)
                    {
                        // 如果未标记 delete,则判定为内存泄露
                        StringCchPrintf(pszOutPutInfo,2*MAX_PATH,_T("%s(%d):检查到内存泄露,内存块(Point=0x%08X,Size=%u)\n")
                            ,g_pMemBlockList[i].m_pszSourceFile,g_pMemBlockList[i].m_iSourceLine,phe.lpData,phe.cbData);
                        OutputDebugString(pszOutPutInfo);
                    }
                    bRecord = TRUE;
                    break;
                }
            }
            // 如果没有在我们维护的列表中发现这个内存块的申请信息,那么输出未记录内存
            if( !bRecord )
            {
                StringCchPrintf(pszOutPutInfo,2*MAX_PATH,_T("未记录的内存块(Point=0x%08X,Size=%u)\n")
                    ,phe.lpData,phe.cbData);
                //OutputDebugString(pszOutPutInfo);
            }
        }

    }
    HeapUnlock(GetProcessHeap());
    OutputDebugString(_T("内存泄露检查完毕.\n"));
    if( bDestroyHeap )
    {
        HeapDestroy(g_hBlockInfoHeap);
    }
}

//__FILE__预定义宏的宽字符版本
#define GRS_WIDEN2(x) L ## x
#define GRS_WIDEN(x) GRS_WIDEN2(x)
#define __WFILE__ GRS_WIDEN(__FILE__)

#define new new(__WFILE__,__LINE__)
#define delete(p) ::operator delete(p,__WFILE__,__LINE__)
#endif

int _tmain()
{
    int* pInt1 = new int;
    int* pInt2 = new int;
    float* pFloat1 = new float;

    BYTE* pBt = new BYTE[100];


    delete pInt2;

    GRSMemoryLeak();
    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-04-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码示意图
  • 效果图
  • 实现代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档