目录
1、VLD工具概述
2、下载并安装VLD
2.1、下载VLD
2.2、安装VLD
3、VLD安装目录及文件说明
3.1、安装目录及文件说明
3.2、关于32位和64位版本的详细说明
4、在工程中引入VLD
5、内存泄漏检测实例讲解
5.1、程序启动报错
5.2、启动调试,查看内存泄漏报告
5.3、vld.ini配置文件的使用
6、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html 内存泄漏是C/C++程序一类常见的问题,内存泄漏的危害比较大,如果内存长时间的持续泄漏,则会导致Out of memory内存耗尽异常,程序进而发生崩溃闪退。对于大型软件来说,业务模块众多、设计复杂,排查起来可能会很费劲,可能使用多种排查手段都难以定位。我们有必要掌握多种排查内存泄漏的方法,在遇到问题时,一种方法无法定位,可以尝试使用其他手段去排查。今天我们来介绍一个内存泄漏工具Visual Leak Detector(简称VLD),详细讲述使用该工具排查内存泄漏问题的完整过程。本文考虑到很有朋友是开发新手,会讲的比较详细一些。
1、VLD工具概述
Visual Leak Detector是一款用于C/C++的免费开源的内存泄露检测工具。相比较其它的内存泄露检测工具,它在检测到内存泄漏时有如下特点:
1)可以输出内存泄漏点的调用堆栈,在函数调用堆栈中会显示具体的文件名及代码行号;
2)可以显示泄露内存中的完整数据;
3)它不是独立运行的exe程序,是一个dll动态库,需要在项目工程中引入该库,并对代码进行重新编译;
4)它的源代码是开源的,使用GNU许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理以及内存泄漏排查机制的读者,是个不错的选择。
从使用的角度讲,VLD简单易用,对于使用者,只要自己的代码中包含VLD的头文件,并将VLD的dll库拷贝到exe主程序的目录中,在debug下正常运行程序就可以检测内存泄漏问题了。从研究角度上讲,VLD源代码是开源的,深入到源码中,可以学习堆内存分片与释放的原理、内存检查的原理机器内存操作的常用技巧等。
2、下载并安装VLD
VLD当前的最新版本是2.5.1,进入VLD的官方页面,下载该版本即可。
2.1、下载VLD
如果找到微软关于VLD的官方页面(https://marketplace.visualstudio.com/items?itemName=ArkadyShapkin.VisualLeakDetectorforVisualC),如下所示:
点击“Get Stated”按钮,进入VLD的github页面:
在页面底部点击vld-2.5.1-setup.exe的超链接去下载,中途会下载失败。
可以到https://kinddragon.github.io/vld/页面,点击“Download Installer”按钮去下载,如下:
2.2、安装VLD
先到网址上下载最新版本的Visual Leak Detector 2.5.1的安装包,下载完直接安装就可以了。在安装过程中会让选择是否将VLD添加到环境变量和Visual Studio中,此处不用勾选,不需要自动帮我们配置,我们在需要使用VLD时手动在项目中配置一下就好了。
3、VLD安装目录及文件说明
VLD并不是一个独立运行的exe程序,只是一个dll库,所以VLD安装包中包含的是VLD编译好的.h头文件、.lib和.dll库文件,它并不是安装一个可以执行的exe程序,只是将这些释放到安装目录中。
3.1、安装目录及文件说明
VLD默认是安装到路径C:\Program Files (x86)\Visual Leak Detector中的,我们可以到安装目录中去看一下,主要包含3个文件夹:
include文件夹中放的是VLD库的头文件,如下:
lib文件夹中存放的是vld.lib文件,有两个版本,一个是Win32的vld.lib,一个是Win64的vld.lib。bin文件夹中存放dll等二进制文件,如下所示:
同样也有32位和64位版本。
类似的安装包,我们在编译libcurl开源代码时也会用到,libcurl库内部会调用到openssl开源库接口,但libcurl库源码中不提供openssl库,我们可以到网址中下载openssl安装包,该安装包中就是包含了openssl的库文件和头文件,即安装目录下就是释放出来的库文件和头文件。
3.2、关于32位和64位版本的详细说明
使用IDE编译Windows程序时,可以编译32位的(对应x86),也可以编译64位的(对应x64)。32位Windows系统中只能运行32位程序,不能运行64位程序。64位Windows系统中可以运行64位程序,也可以运行32位程序(提供对32程序的兼容)。
较老的Windows系统可能还是32位,比如部分Win7系统还是32位的。不过比较新的Win10、Win11系统全部都是64位系统了。很多软件为了兼容32位Windows系统,直接将软件编译成32位程序,这样程序在32位和64位Windows系统中都可以运行。
也有不少程序,分别提供了32位安装包和64位安装包,这样用户可以根据自己的需要选择对应的版本安装。如果是32位Windows系统,则只能安装32位安装包;如果时64位Windows系统,则32位和64位安装包都可以安装。
从开发者的角度,有两点需要注意一下:
1)系统32位和64位程序进程分配的虚拟内存大小有较大差异
对于32位程序,系统会给启动的程序分配2的32次方的虚拟内存,即4GB。对于64位程序,系统会给程序进程分配2的64次方的虚拟内存,即虚拟内存非常大,比32位程序4GB的虚拟内存要大的多。对于32位程序,2GB是用户态的内存,2GB是内核态的内存,对于大型软件,运行过程中会占用较大的内存空间,有可能会出现虚拟内存不够用的情况,如果虚拟内存不够用,则会产生Out of memory内存耗尽的异常。之前我们在讲内存泄露时,也讲到过这个内存耗尽的异常。
2)位数不同的exe和dll库不能混用
32位程序不能使用64位dll文件,64位程序不能使用32位dll文件,因为位数不同,寻址范围是不同的。32位程序要选用32位dll文件,64位程序要选用64位dll文件。所以VLD提供了32位和64位两个版本的库,使用者根据自己程序的位数,选择对应的版本。
4、在工程中引入VLD
Visual Leak Detector不是一个exe可执行程序,和能直接运行的工具有所不同,它是一个dll动态库,需要集成到我们的代码中,重新编译代码,然后在程序运行的过程中进行检测。
我们在引用VLD时,就像引用普通的dll库一样,先include库的头文件(vld.h),再引入库的.lib文件(vld.lib,编译链接时需要用到),然后将dll文件拷贝到exe主程序的目录中。
1) 包含vld.h头文件(用于编译)
在包含vld.h头文件之前,需要将vld.h头文件所在的路径设置到工程属性中:C/C++ -> 常规,在附加包含目录中添加vld.h头文件所在的路径,如下所示:
这样编译代码时就会到上述路径下去找到vld.h头文件了。
2)引入vld.lib文件(用于链接)
在引入vld.lib文件之前,需要将vld.lib文件所在的路径设置到工程属性中:链接器 -> 常规,在附加库目录中添加vld.lib文件所在的路径,如下所示:
这样编译链接时就会到上述路径中去找到vld.lib文件,进行链接了。此处,我们不用手动将vld.lib文件引入到工程代码中,因为vld.h头文件中已经帮我们自动引入了,如下所示:
3)拷贝vld.dll库文件到exe主程序目录中(用于运行)
到安装目录下的bin文件夹中将dll及其他的文件拷贝到exe主程序的目录中。
编译代码,运行程序,即可查看检测结果。
5、内存泄漏检测实例讲解
之前创建了一个win32控制台工程TestMemLeak,然后在main函数中添加一行动态申请内存的代码,如下所示:
在main函数退出时故意没有释放内存,测试一下VLD的检测效果。
5.1、程序启动报错
按上面讲的那样,将头文件和.lib文件的路径设置到工程中,然后将vld.h头文件包含进来。然后编译代码,启动调试,结果启动时报错了
弹出如下的提示框:
程序无法启动,对应的异常码为0xc0150002。
为了搞清楚这个错误码的含义,直接在VS中输入之前熟悉的错误码宏:STATUS_STACK_OVERFLOW
然后go到对应的系统异常码定义的头文件中,对应的路径为:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\ntstatus.h
尝试在该头文件中搜索异常码0xc0150002,果然找到了,如下:
系统无法处理应用程序中的绑定信息。难道是在拷贝bin目录下的文件时,不仅要拷贝vld_x86.dll,是不是要把目录下的其他文件也拷贝过去?
于是手动又将dbghelp.dll和Microsoft.DTfW.DHL.manifest拷贝到exe主程序的目录中,重新运行exe程序就好了,就不再报错了。
5.2、启动调试,查看内存泄漏报告
因为main函数比较简单,进入后立即退出了,程序也就退出了。然后在Visual Studio的输出窗口中,看到了:Visual Leak Detector detected memory leaks!,即VLD检测到了内存泄漏,如下所示:
将发生内存泄漏的地址及大小打印了出来:
---------- Block 1 at 0x01497020: 1024 bytes ----------
同时也将泄漏代码所在线程的函数调用堆栈打印出来了,然后还将内存泄漏的那段内存中的内容显示出来了。双击图中的那行代码,就跳转到发生内存泄漏的那行代码:
5.3、vld.ini配置文件的使用
在VLD的安装目录中还有个vld.ini配置文件,如下所示:
这个配置文件中可以对VLD进行一些配置,如果要用这个文件,需要将之拷贝到exe主程序的目录中。
可以使用ReportTo配置项,默认情况下分析报告是输出到Debugger(调试器)窗口中的,如下所示:
可以设置成both,这样既会输出到调试器的窗口中,也可以将检测报告输出到文件中。如果ReportTo选项设置为both,还要设置ReportFile选项,指定导出到的文件名称,比如:
还有其他的配置选项,文件中有详细的注解,感兴趣可以去自行试验一下。
6、最后
也可以使用Windbg中的umdh程序去检测内存泄漏,我们在项目中用过,效果还行。使用umdh检测内存泄漏的详细过程,可以参见我之前写的文章:
使用Windbg定位Windows C++程序中的内存泄漏https://blog.csdn.net/chenlycly/article/details/121295720使用umdh分析时,不用重新编译代码,比较方便。而使用VLD时,需要重新编译代码,会比较麻烦。特别是大型软件,软件中包含了多个dll模块,不同的dll模块是由不同的开发组维护的,让所有模块都用VLD重新编译一下,需要统筹协调,也不是件容易的事。
另外,这些内存泄漏检测工具不是万能的,没法检测出所有场景下的泄漏。因为大型软件包含了多个模块,代码比较繁琐复杂,有些模块会一上来就会申请内存,一直不释放;有的可能使用到了内存池,内存管理比较复杂。这会导致工具出现误检测,到底是不是真正的内存泄漏点,还要结合具体的代码和业务去分析。一个工具检测不出来,还需要尝试使用其他工具,甚至需要把很多工具都尝试一遍。
对于分析内存泄漏的工具,若干年前有个强大的工具叫BoundsChecker,但这个工具很久不维护了,新版本的Visual Studio已经没法再使用了。此外,Visual Studio 2019 V19.6版本开始引入了google的内存分析工具AddressSanitizer:
也可以尝试使用AddressSanitizer。对于如何在VS中如何使用AddressSanitizer内存分析工具,可以看一下微软官方文章的详细说明:
在Visual Studio中使用AddressSanitizerhttps://docs.microsoft.com/zh-cn/cpp/sanitizers/asan?view=msvc-170 要使用Visual Studio中集成的AddressSanitizer,需要将IDE升级到Visual Studio 2019 V19.6或以上版本,要将大型软件从上到下的多个模块统一升级到某个版本,需要一定的时间和人力的。