C++内存泄漏问题
简单来说,就是new/malloc后的内存,没有做delete/free。
这样的问题,难以发现,难以调试。在Windows上,有两个工具,GFlags和UMDH,可以用来发现内存泄漏问题。
我们先写一个C++内存泄漏的程序,然后详细讲解如何用GFlags和UMDH发现内存泄漏问题。
int *getmem(void)
{
int *p = (int *)malloc(32);
return p;
}
int _tmain(int argc, _TCHAR* argv[])
{
int i;
scanf("%i", &i);//wait for mem leak
int *x = getmem();//mem leak here
scanf("%i", &i);//wait to exit
return 0;
}
很明显,这里malloc的内存,没有释放。用VS2010将程序编译为Win32控制台程序:mem_leak.exe。
程序中之所以设置两个scanf,是为了让程序停下来:
- 启动程序时,不存在内存泄漏,此时可以dump堆信息1
- 输入任意数字,触发内存泄漏,程序还能停在这里,便于dump堆信息2
UMDH的原理,就是对堆信息1和堆信息2做diff,通过diff中的allocate与deallocate次数匹配,就能发现内存泄漏问题
GFlags和UMDH具体使用步骤
参考这个例子进行试验:
实验环境:Windows10 64bit, VS2010
我的具体步骤:
(1)下载WindowsSDK,从这里:https://developer.microsoft.com/zh-cn/windows/downloads/windows-8-1-sdk
(2)运行WindowsSDK安装包,选择“Debugging Tools for Windows”。因为UMDH和GFlags都是Debugging Tools for Windows中的工具。里面含有的工具在这里能看到:https://msdn.microsoft.com/en-us/library/windows/hardware/ff543998(v=vs.85).aspx#installation_directories
(3)UMDH 和 GFlags被安装在C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x64(设置环境变量)
(4)在CMD中,输入
gflags /i mem_leak.exe +ust
(这是使用UMDH之前的准备工作,具体参考https://msdn.microsoft.com/en-us/library/windows/hardware/ff553431(v=vs.85).aspx)(5)在CMD中,输入
set _NT_SYMBOL_PATH=C:\Windows\symbols
(这是使用UMDH之前的准备工作,具体参考https://msdn.microsoft.com/en-us/library/windows/hardware/ff553431(v=vs.85).aspx)(6)运行mem_leak.exe,在任务管理器找到进程号processed。启动程序时,不存在内存泄漏
(7)在CMD中,执行
umdh –p:processed –f:mem_leak.before.dmp
(启动程序时,不存在内存泄漏,此时可以dump堆信息1)(8)在运行的mem_leak.exe中,输入任意数字,触发memory leak行为。触发内存泄漏,程序还能停在这里,便于dump堆信息2。
(9)在CMD中,执行
umdh –p:processed –f:mem_leak.after.dmp
(dump堆信息2。)(10)在CMD中,执行
umdh -d mem_leak.before.dmp mem_leak.after.dmp > diff.dmp
查看diff.dmp,具体信息如下
// Debug library initialized ...
E60000-E65FFF DBGHELP: mem_leak - private symbols & lines
.\mem_leak.pdb
77410000-77588FFF DBGHELP: ntdll - export symbols
75A40000-75B2FFFF DBGHELP: KERNEL32 - export symbols
76F60000-770D5FFF DBGHELP: KERNELBASE - export symbols
72E50000-72F0EFFF DBGHELP: MSVCR100 - export symbols
73480000-73502FFF DBGHELP: TmUmEvt - export symbols
76580000-76585FFF DBGHELP: PSAPI - export symbols
76C80000-76CC3FFF DBGHELP: SHLWAPI - export symbols
764C0000-7657DFFF DBGHELP: msvcrt - export symbols
76010000-761C9FFF DBGHELP: combase - export symbols
75E90000-75F3BFFF DBGHELP: RPCRT4 - export symbols
758E0000-758FDFFF DBGHELP: SspiCli - export symbols
758D0000-758D9FFF DBGHELP: CRYPTBASE - export symbols
75870000-758C8FFF DBGHELP: bcryptPrimitives - export symbols
75FC0000-76002FFF DBGHELP: sechost - export symbols
75BF0000-75D3CFFF DBGHELP: GDI32 - export symbols
75D50000-75E8FFFF DBGHELP: USER32 - export symbols
75F40000-75FBAFFF DBGHELP: ADVAPI32 - export symbols
76CD0000-76CFAFFF DBGHELP: IMM32 - export symbols
772F0000-7740FFFF DBGHELP: MSCTF - export symbols
731D0000-732BAFFF DBGHELP: tmmon - export symbols
74300000-74307FFF DBGHELP: VERSION - export symbols
//
// Each log entry has the following syntax:
//
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations
// ... stack trace ...
//
// where:
//
// BYTES_DELTA - increase in bytes between before and after log
// NEW_BYTES - bytes in after log
// OLD_BYTES - bytes in before log
// COUNT_DELTA - increase in allocations between before and after log
// NEW_COUNT - number of allocations in after log
// OLD_COUNT - number of allocations in before log
// TRACEID - decimal index of the stack trace in the trace database
// (can be used to search for allocation instances in the original
// UMDH logs).
//
+ 32 ( 32 - 0) 1 allocs BackTrace9F3A48
+ 1 ( 1 - 0) BackTrace9F3A48 allocations
ntdll!RtlWalkHeap+188
ntdll!WinSqmEventWrite+D7AE
ntdll!RtlAllocateHeap+28
tmmon!SleepMon+273AB
MSVCR100!malloc+36
mem_leak!wmain+1E (c:\users\xxx\documents\visual studio 2010\projects\mem_leak\mem_leak\mem_leak.cpp, 17)
mem_leak!__tmainCRTStartup+122 (f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c, 552)
KERNEL32!BaseThreadInitThunk+24
ntdll!RtlSetCurrentTransaction+D4
ntdll!RtlSetCurrentTransaction+9F
Total increase == 32 requested + 24 overhead = 56
发现+ 1 ( 1 - 0) BackTrace9F3A48 allocations
。这说明有1次allocate内存未释放,还能从diff.dmp中,发现内存泄漏发生在mem_leak.cpp, 17行。
调试无内存泄漏问题的程序
经测试,如果申请的内存都被释放,diff.dmp中是不会出现这样的trace的。将这个例子main中代码改为如下:
int _tmain(int argc, _TCHAR* argv[])
{
int i;
scanf("%i", &i);//wait for mem leak
int *x = getmem();//mem leak here
free(x);
scanf("%i", &i);//wait to exit
return 0;
}
继续执行上面使用UMDH的10个步骤,发现diff.dmp中,出去注释,则只有如下一行有价值的信息:
Total decrease == 0 requested + 0 overhead = 0
总结
使用GFlags和UMDH,可以用来发现Windows下C++程序的内存泄漏问题。
但UMDH在Win7中使用,自己一直报异常,我没能成功使用。反而在Win10下,UMDH使用正常。