VLD简介
由于C/C++语言没有所谓的垃圾收集器,内存的分配和释放都需要程序员自己来控制,这会给C/C++程序员带来一定的困难。当您的程序越来越复杂时,它的内存管理也会变得越来越困难。内存泄漏、内存越界是最常见的内存问题之一。
内存泄漏如果不是很严重的话,在短时间内对程序不会造成太大的影响,而且在进程终止的时候,所有分配的内存都会释放掉。但是对于长时间运行的程序,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其它程序的正常运行。
此外,内存问题存在一个共同的特点,它本身并不会有很明显的现象,当有异常出现时就很难检查问题的原因所在,这给调试内存问题带来了很大的难度。
VLD是一款用于VisualC++的免费内存泄漏检查工具。可以在codeproject.com网站上找到,相比其它的内存泄漏哦给你根据,他在检查内存泄漏的同事,还具有如下特点:
1) 可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在的文件及行号;
2) 可以得到泄漏内存的完整数据;
3) 恶意设置内存泄漏报告的级别;
4) 它以动态库的形式提供,无需编译源代码,只需要很小的改动程序;
5) 源代码使用GNU许可发布,并有详细的文档及其注释。
从使用的角度讲,VLD简单易用,对于使用者自己的代码中唯一需要修改的地方是#include VLD的头文件后正常运行自己的程序就可以发现内存问题。从研究角度上讲,如果输入到VLD源代码,可以学习到堆内存分片与释放的原理、内存检查的原理机器内存操作的常用技巧等。
VLD使用
VLD网址:http://vld.codeplex.com/
http://www.codeproject.com/Articles/9815/Visual-Leak-Detector-Enhanced-Memory-Leak-Detectio
下载Visual LeakDetector,当前版本2.2.3,打开Visual C++ IDE的"工具"→"选项"→"项目和解决方案"→"VC++ 目录",在"包含文件"中增加VLD的头文件路径"include"路径,在"库文件"增加VLD库文件的"libWin32"路径,此外动态库的"inWin32"路径在安装时已经添加到环境变量里面了,若是未添加,则需要手动拷贝"inWin32"下的文件到可执行文件所在的目录中(拷贝的文件有dbghelp.dll/Microsoft.DTfW.DHL.manifest/vld_x86.dll/vld.ini)。
接下来需要将VLD加入到自己的代码中。方法很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件中包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。
示例程序:
#include<vld.h> // 包含VLD的头文件
#include<stdlib.h>
#include<stdio.h>
void f()
{
int *p = new int(0x12345678);
printf("p=%08x, ", p);
}
int main()
{
f();
return 0;
}
注:VLD只能在Windows下使用,在包含vld.h头文件时增加预编译选项。
注:在Release模式下,不会链接VisualLeak Detector。
注:Visual LeakDetector有一些配置项,可以设置内存泄露报告的保存地(文件、调试器),拷贝"Visual Leak Detector"路径下的vld.ini文件到执行文件所在的目录下(在IDE运行的话,则需要拷贝到工程目录下),修改以下项:
ReportFile =.memory_leak_report.txt
ReportTo = both
VLD工具原理
下面我们来看看VLD是如何工作的。在VisualC++中内置工具CRT Debug Heap工具,在使用Debug版本分配内存时,它会在内存块中记录分配该内存的文件名和行号。当程序退出时CRT会在main函数返回时做一些清理工作,此时检查调试堆内存,如果仍然有内存没释放,则一定存在内存泄漏问题。从这些没有被释放的内存块的头中可以得到文件名和行号。这种静态的方法可以检查出内存泄漏,但是不知道泄漏究竟是怎么发生的,也不知道该内存分配语句是如何被执行到的,想要了解这些必须对内存分配过程进行动态跟踪。VLD就是这样做的,在每次内存分配的时候记录其上下文,当程序退出时对检测到的内存泄漏查找其上下文信息,并转换成报告输出到Output中。
初始化
VLD要记录每次的内存分配,它通过Windows提供的分配钩子allocation hooks来监视调试堆内存的分配。它是一个用户自定义的回调函数,在每次从堆中分配内存之前被调用,在初始化是VLD使用_CrtSetAllocation注册这个钩子函数。
全局变量在程序初始化时就初始化,如果将VLD作为一个全局变量就可以与程序一起启动,但是C/C++并没有约定全局变量初始化的顺序,如果其它全局变量的构造函数中有内存分配则可能无法检测到。因此,VLD使用C/C++提供的#pragma init_seg来减少其它全局变量在它之前进行初始化。根据#pragma init_seg的定义,全局变量初始化分为3个阶段,首先是compiler阶段,一般进行C语言运行时库的初始化;然后是lib段,一般用于第三方类库的初始化扽;最后是user段,大部分的初始化都在这个阶段进行。
记录内存分配
一个内存分配钩子函数需要具有如下的定义:
int AllocHook(int allocType, void*userData, size_t size,int blockType, long requestNumber, onst unsigned char*filename, int lineNumber);
该函数需要在VLD初始化时被注册,每次从堆中分配内存前被调用,它需要处理的事情就是记录下此时的调用堆栈和此时堆内存分配的唯一标识requestNumber。
得到当前堆栈的二进制表示并不是很复杂的事情,但是因为不同的体系结构、不同的编译器、不同的操作系统所产生的堆栈内容是不一样的,要解释堆栈并得到整个函数的调用过程比较复杂。不过Windows提供了一个StackWalk64函数可以获得堆栈的内容。
VLD是常用的C/C++内存泄漏检查工具,可以在ViusalC++中使用,在Viusal Studio 2008和2010中使用需要注意两点:
1) 版本问题:VLD已经更新到2.2版本,修正了许多bug,而且在2010版本下工作良好,VisualC++ 6.0推荐使用1.0版本,1.9b版本不是很稳定不建议使用,2.2版本的下载网址为http://vld.codeplex.com.
2) 设置变化:VC++Directories设置已经变化位置,在2010中设置过程如下:
View | Other Window | Property Manager
Go to "VC++ Directories" settings
Set include folder path
Set lib folder path
点OK,我们就设置好了include和lib目录。
使用问题
问题1:VLD 1.9
在vista下使用vld的使用,总是出现错误无法正常工作,后来经过搜索,在http://www.codeproject.com/KB/applications/visualleakdetector.aspx
上的评论中找到了解决的方法:
评论“Solution forrunning 1.9 beta on Visual Studio 2008 with Vista ”给出了解决方法:
评论1:
VLD keptcrashing when trying to use 1.9g beta on Windows Vista, visual studio 2008. Itried all the suggestions on here and nothing worked. But I finally figured itout.
when you make a project in visual C++ 2008,it sets some strange advanced Linker properties that cause VLD to crash:
I changedLinker->Advanced->Randomized Base Address from Enable Image Randomization(/DYNAMICBASE) to Disable Image Randomization (/DYNAMICBASE:NO)
Then I changed Linker->Advanced->DataExecution Prevention from Image is compatible with DEP (/NXCOMPAT) to Default
And now it works perfectly
Please let me know if this helped you!It'll make me feel better for spending a whole day trying to get it working!
-Nadav
评论2:
The base address randomization seems to benot necessary. Just disable DEP.
大致的意思是说,只需要禁用DEP即可,
在工程的“属性”->“链接器”->“高级”->数据执行保护(DEP),设为“默认”(default)或者“映像与 DEP 不兼容(/NXCOMPAT:NO)“ 即可。(修改后好像不可用)。
注:这个选项只针对Vista有效!!!
问题2:VLD 2.2.3
在项目中使用了visual leak detector,调试时程序无法启动报错“应用程序正常启动失败(0xc0150002)”。
解决流程:
查看vs输出信息最后一条是:
Theprogram '[3980] MobileSignalAnalyzer.exe: Native' has exited with code-1072365566 (0xc0150002)
在网上多方查找有:
http://blog.csdn.net/evilswords/article/details/5698851
http://blog.csdn.net/brook0344/article/details/6685724
这两篇有解决办法,就是把VLD中的这两个复制到执行文件夹下就正常了
Microsoft.VC90.CRT.manifest
Microsoft.DTfW.DHL.manifest
产生原因:
VC2003、VC2005、VC2008及其后续版本,对底层最基本的CRT、MFC、ATL库都进行了重构,为了避免不同版本的库引起冲突,重构后的库文件一般放在C://windows/WinSxS 文件夹中,并用特定的文件夹/文件名称进行标识;
与VC6不同, VC2003、VC2005、VC2008及其后续版本,引入了manifest清单的概念,即应用程序编译后会同时生成对应的.manifest文件,并将该.manifest文件作为资源编译到dll或者exe中去。.manifest文件实际上是一个XML格式的文本文件,里面记录了dll或exe中要引用的CRT、MFC、ATL库的版本和名称。VC6编译的应用程序对CRT、MFC、ATL的dll都是直接调用,而VC2003、VC2005、VC2008编译的程序都是先查询编译到资源中的manifest中的记录,然后按照记录提供的版本和名称去搜寻对应的CRT、MFC、ATL库以及随库发布的.manifest文件,搜寻的路径包括当前目录、C://windows/WinSxS等等,如果没有找到对应的库文件,则提示“应用程序正常初始化失败”。