排查分两大部分:
1.代码静态分析,通过Code Review查找不合规范的代码点;
2.运行目标软件,结合内存监控工具,分析目标软件的代码,定位内存泄漏点。
目前能找到的代码静态分析软件:Coverity代码静态检查工具(Synosys公司的)、Fortify SCA(擅长JAVA,C++也能分析)、PVS-Studo、PC-lint。
内存监控工具:
Bounds check、Deleaker、CRTDBG(VS自带的,编译DEBUG版本,内存泄漏时会在Debug窗口中有打印信息)。
Deleaker:
使用简单,功能强大,除了内存泄漏还可以检查GDI泄漏,安装包自带VS插件,同时也支持其他IDE。
下载地址:https://www.deleaker.com/
免费试用14天,年费$299
安装步骤:按照默认步骤安装。
使用方法:安装完后,VS中会出现一个Deleaker的菜单项,点击打开Deleaker的界面,勾选Enable后,运行调试,则Deleaker也会进入分析状态,结束调试则会完成分析结果。
Deleaker分析代码有两种模式,一种是托管代码,一种是非托管代码,根据实际情况选择。
Deleaker界面如下,常用按钮已标记出来:
Unmanaged Code Profiling Mode :非托管代码模式
.Net Profiling Mode :托管代码模式
Allocation Types:可以配置要分析的缺陷类型,比如内存、GDI。
Take Snapshot是用来进行暂存内存快照的,和VS2017诊断工具差不多。
Module可以按照模块筛选分析结果,Leak Type可以按照缺陷类型筛选分析结果。
Options项是一些高级配置,一般使用默认配置就可以了。
最下方两个空白窗口分别是分析结果和调用栈信息,选中一条分析结果就可以精确定位到泄漏的代码行了。
Viual Leak detector:
Viual Leak detector安装后,要在VS中设置相应的头文件和库路径,在Debug模式下如果要检测相应源文件的内存泄露,则加上"#include <vld.h>"即可;
将.h文件拷贝到Visual C++的默认include目录下,将.lib文件拷贝到Visual C++的默认lib目录下,便安装完成了。(因为版本问题,如果使用windows 2000或者以前的版本,需要将dbghelp.dll拷贝到你的程序的运行目录下,或其他可以引用到的目录)。接下来需要将其加入到自己的代码中。方式很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。
在检测内存泄露,可以在VS的输出窗口查看输出信息。
CRTDbg:
工具局限性:
1)对于调试非MFC程序,不能打印文件名和行号。对于一个比较大的程序,没有这些信息,解决问题将变得十分困难。
2)由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资 源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。
3)为了能记录内存块是在哪里 分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。
工具使用:
没有工具的情况下,使用crtdbg.h中的api也是可以的,但是有之前说的两个局限性。
在MFC中可以看到在程序退出的时候,输出框内结尾部分输出内存泄露,并且点击可以跳转到内存泄露的代码处。
A) _CrtSetDbgFlag函数,这个函数用于控制debug模式下堆管理的分配行为;(函数详细信息参考:http://msdn.microsoft.com/zh-cn/library/5at7yxcs.aspx)
在main函数开始处添加该函数,则如果出现内存泄露Debug结束后,输出框将输出打印信息。
_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
//_CRTDBG_REPORT_FLAG:表示获取当前的标示位 //_CRTDBG_LEAK_CHECK_DF:表示检测内存泄露
B) 显示内存泄露所在的文件以及行
能够知道有内存泄露是不够的,更需要的信息是哪里内存泄露了?
我们可以在每个源文件的开头定义写这样一条宏定义:
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__) //根据__FILE___和__LINE__能够确定文件和行
C) 显示内存泄露处的堆栈,(函数详细信息参考:http://technet.microsoft.com/zh-cn/library/aa246759)
long _CrtSetBreakAlloc( long lBreakAlloc ); //lBreakAlloc,在申请的堆区序号为lBreakAlloc处设置一个断点
此函数在指定的申请堆区空间次序处(即lBreakAlloc)设置断点;这个函数结合"A)"中提到的{150},比如使用方法:
_CrtSetBreakAlloc(150); //则在第150次申请堆空间时候设置断点 .
这样就可以看到函数调用栈,从而帮助我们更加精确的定位程序泄露的位置.