重载 new、delete 检测内存泄露

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

代码示意图

2016-04-15_114609

效果图

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

2016-04-15_114209

实现代码

#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;
}

发表评论