内存泄露带来的问题我想我就不必多少了,检测内存泄露有很多种方法,比如使用一些智能指针。但本文介绍的方法有些不同,我们将自己维护一个数组列表,记录下 new 内存时代码所在的文件、行号、以及大小、和是否已经被 delete 信息,将这些信息放到我们维护的数组中,当程序要检查内存泄露或者程序退出时,我们遍历整个堆内存,并把每一个堆内存块在我们维护的数组中遍历,如果发现某些内存并没有被标记为 delete 状态,那么则判定为泄露。
代码示意图
效果图
这里的代码巧妙的用到了 Visual Studio 一个小技巧,在输出窗口中输入文件 行号后,我们可以双击这一行的内容快速定位到文件和具体行。先看一下效果图。
实现代码
代码语言: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=0xX,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=0xX,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;
}