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

代码示意图

2016-04-15_114609

效果图

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

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#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;
}