【Visual Leak Detector】QT 中 VLD 输出解析(二)

打印 上一主题 下一主题

主题 503|帖子 503|积分 1509

说明

使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。

目录


1. 使用方式

在 QT 中使用 VLD 的方法可以查看另外几篇博客:
本次测试使用的环境为:QT 5.9.2MSVC 2015 32bitDebug 模式,VLD 版本为 2.5.1,VLD 配置文件不做任何更改使用默认配置,测试工程所在路径为:E:\Cworkspace\Qt 5.9\QtDemo\testVLD。
2. 有一处内存泄漏时的输出报告(int 型)

写一个有一处内存泄漏的程序,如下:
  1. #include <QCoreApplication>
  2. #include "vld.h"
  3. void testFun()
  4. {
  5.     int *ptr = new int(0x55345678);
  6.     printf("ptr = %08x, *ptr = %08x", ptr, *ptr);
  7. }
  8. int main(int argc, char *argv[])
  9. {
  10.     QCoreApplication a(argc, argv);
  11.     testFun();
  12.     return a.exec();
  13. }
复制代码
程序运行时,在标准输出窗会输出以下结果:
  1. ptr = 0127b7a0, *ptr = 55345678
复制代码
程序运行结束后,检测到了内存泄漏,VLD 会输出以下报告(本例中出现一处内存泄漏),第 1~3 行显示 VLD 运行状态,第 4~21 行显示泄漏内存的详细信息,第 22~24 行总结此次泄漏情况,第 25 行显示 VLD 退出状态。
  1. Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
  2. Visual Leak Detector Version 2.5.1 installed.
  3. WARNING: Visual Leak Detector detected memory leaks!
  4. ---------- Block 1 at 0x0127B7A0: 4 bytes ----------
  5.   Leak Hash: 0xEB4D3A14, Count: 1, Total 4 bytes
  6.   Call Stack (TID 22408):
  7.     ucrtbased.dll!malloc()
  8.     f:\dd\vctools\crt\vcstartup\src\heap\new_scalar.cpp (19): testVLD.exe!operator new() + 0x9 bytes
  9.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (6): testVLD.exe!testFun() + 0x7 bytes
  10.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (16): testVLD.exe!main()
  11.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
  12.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
  13.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
  14.     f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
  15.     KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
  16.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
  17.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  18.   Data:
  19.     78 56 34 55                                                  xV4U.... ........
  20. Visual Leak Detector detected 1 memory leak (40 bytes).
  21. Largest number used: 40 bytes.
  22. Total allocations: 40 bytes.
  23. Visual Leak Detector is now exiting.
复制代码
第 1 行表示 VLD 读取的配置文件路径,可以根据路径找到该文件,然后更改里面的相关配置,获得想要的效果。
第 2 行表示 VLD 2.5.1 在程序中初始化成功。
第 3 行表示本次运行检测到了内存泄漏。
第 4 行中,Block 1 表示本块内存是在堆上分配的第 1 个内存块,0x0127B7A0 表示该内存块的首地址,与标准输出窗输出的 ptr = 0127b7a0 一致,4 bytes 表示该内存块的大小,这一行输出了泄漏内存块的地址信息和大小信息。
第 5 行中,Leak Hash: 0xEB4D3A14 是由泄漏块大小及调用堆栈信息计算出的唯一标识符,如果在报告中看到相同的 Leak Hash,这表示这些泄漏块具有相同大小和相同的调用堆栈。Count: 1 是发生泄漏的计数,使用默认配置时全部等于 1,可以将配置文件中的参数 AggregateDuplicates 设置为 yes 来合并显示具有相同 Leak Hash 值的的泄漏块信息。Total 4 bytes 是此内存块的泄漏大小,与第 4 行一致。这一行输出了泄漏内存块的唯一标识符、泄漏频次、大小信息。
第 6 行中,Call Stack 表示接下来的几行是产生泄漏的调用堆栈,(TID 22408) 表示产生此内存泄漏块函数所在线程的 TID 为 22408,据此来指示发生内存泄漏的线程。在调试多线程程序时,TID 信息很有帮助,它能帮助确定泄漏所在线程。
第 7 行中,ucrtbased.dll 是一个系统库,提供了各种标准 C 和 C++ 函数的实现,包括 malloc(),这个函数用于在运行时动态分配内存。它处于调用栈顶,表示此内存块是使用 ucrtbased.dll 库中的 malloc() 分配的,然后传递给第 8 行 testVLD.exe 程序中的 operator new()。
第 8 行中,f:\dd\vctools\crt\vcstartup\src\heap\new_scalar.cpp 是从 MSVC 中获取的调试信息,这个路径是内置在 Visual C++ Runtime Library 中的,并不代表 new_scalar.cpp 的真实路径,它的真实路径一般在 Visual Studio 的安装目录下。在我电脑上:

  • Visual Studio 2015 使用的 new_scalar.cpp 文件真实路径为 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\new_scalar.cpp;
  • Visual Studio 2019 使用的 new_scalar.cpp 文件真实路径为 D:\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\crt\src\vcruntime\new_scalar.cpp。
在不同系统上的相同版本的 Visual C++ Runtime Library 中,这个内置路径通常是一样的。(19) 表示 operator new() 函数中分配内存的代码位于 new_scalar.cpp 文件的第 19 行,最后面的 + 0x9 bytes 表示从 operator new() 函数开始到导致泄漏产生的指令的内存偏移量,这些信息在调试时很有用,可以帮助快速定位到确切代码行。
第 9 行中,e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (6): testVLD.exe!testFun() + 0x7 bytes 表示 main.cpp 位于 e:\cworkspace\qt 5.9\qtdemo\testvld 路径下,这与项目实际路径是一致的,差别只是 VLD 将其全部转成了小写字母形式,testFun() 函数中分配内存的代码位于 main.cpp 的第 6 行,这与实际情况完全一致。最后面的 + 0x7 bytes 表示从 testFun() 函数开始到导致泄漏产生的指令的内存偏移量,这个信息据说是多用于汇编调试中,与实际是否能对上还没仔细研究过。
第 10 行中,e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (16): testVLD.exe!main() 表示 main() 函数中分配内存的代码位于 main.cpp 的第 16 行,没有提供指令的内存偏移信息,这与实际情况(第 14 行)有些差异,不过第 14 与第 16 行之间并没有别的代码,造成这种差异的原因有待深究,但对于定位泄漏点所在位置已经够用了。
第 11~14 行,跟踪显示了启动程序所调用的函数链,其中 mainCRTStartup() 函数是入口点。
第 15~17 行,跟踪显示了程序启动时所调用的 Windows 操作系统函数,BaseThreadInitThunk() 一般都会出现在调用栈底,它是 Windows 进程中所有用户模式线程的入口点,系统调用它在进程中启动一个新线程,并由它调用程序的主函数。ntdll.dll 中的 RtlGetAppContainerNamedObjectPath() 函数被调用了两次,但指令的内存偏移量不同(分别是 0x11E bytes 和 0xEE bytes),这也是一个 Windows 操作系统函数,用于检索与进程相关的应用程序容器名称,由 Windows 系统的各个部分和其他需要知道应用程序容器名称的程序调用,关于 Windows 容器的介绍,可以查看 Microsoft Windows 和容器
第 18~19 行,分别用十六进制及 ASCII 字符显示了泄漏内存块中信息,内存初始化时赋的初始值为 0x55345678,计算机默认使用小端字节序,因此在内存中各字节的十六进制初始值分别为 78 56 34 55,转化为十进制数,0x78、0x56、0x34、0x55 分别为 120、86、52、85,查找 ASCII 码表,可得这四个字节对应的 ASCII 字符分别为 x、V、4、U,与 VLD 的输出完全一致。VLD 在显示内存内容时,每行最多显示 16 个字节,但这次只泄漏了 4 个字节,因此在显示上第 19 行中间有 12 个字节的空白位,行尾有 12 个占位点(.... ........)。
第 22 行,表示本次运行检测到 1 处内存泄漏,泄漏的总大小为 40 bytes,这里面不光包含用于 int 存储的 4 bytes,还包含用于管理追踪这块内存的另外 36 bytes,因此,虽然代码只请求了 4 bytes 的内存,但程序实际上为此分配了 40 bytes 的内存。可以查看以下资料,这两个网站内容一样,第一个是国外源博客,第二个是国内某爬虫网站盗取的博客。(实际测试发现,使用 32 bit 的编译器时,这个管理头的大小为 36bytes,当使用 64 bit 的编译器时,大小变为 52bytes。)
第 23 行,表示本次运行中单次分配的最大内存大小,为 40 bytes,原因看前一条。在实际使用过程中,这个 Largest number used 有时候跟实际情况对不上,又好像表示本次运行中分配的连续堆内存最大宽度,有兴趣的可以深入研究。
第 24 行,表示本次运行中堆上分配内存的总大小,为 40 bytes,即代码申请 int 的 4 bytes,和管理头占用的 36 bytes。
第 25 行,表示 VLD 正常退出。
在程序的第 6 行加个断点,按 F5 进入调试状态,结果如下,调用堆栈中各函数的名称、所属文件、所在行号、调用顺序都和 VLD 一致。

3. 有一处内存泄漏时的输出报告(int 数组型)

写一个有一处内存泄漏的程序,如下:
  1. #include <QCoreApplication>
  2. #include "vld.h"
  3. void testFun()
  4. {
  5.     int *ptr = new int[10];
  6.     ptr[0] = 0x64568932;
  7.     printf("ptr = %08x", ptr);
  8. }
  9. int main(int argc, char *argv[])
  10. {
  11.     QCoreApplication a(argc, argv);
  12.     testFun();
  13.     return a.exec();
  14. }
复制代码
程序运行时,在标准输出窗会输出以下结果:
  1. ptr = 00ab4340
复制代码
程序运行结束后,检测到了内存泄漏,VLD 会输出以下报告(本例中出现一处内存泄漏),第 1~3 行显示 VLD 运行状态,第 4~23 行显示泄漏内存的详细信息,第 24~26 行总结此次泄漏情况,第 27 行显示 VLD 退出状态。
  1. Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
  2. Visual Leak Detector Version 2.5.1 installed.
  3. WARNING: Visual Leak Detector detected memory leaks!
  4. ---------- Block 1 at 0x00AB4340: 40 bytes ----------
  5.   Leak Hash: 0x39CB72AB, Count: 1, Total 40 bytes
  6.   Call Stack (TID 29256):
  7.     ucrtbased.dll!malloc()
  8.     f:\dd\vctools\crt\vcstartup\src\heap\new_array.cpp (15): testVLD.exe!operator new[]() + 0x9 bytes
  9.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (6): testVLD.exe!testFun() + 0x7 bytes
  10.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (17): testVLD.exe!main()
  11.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
  12.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
  13.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
  14.     f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
  15.     KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
  16.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
  17.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  18.   Data:
  19.     32 89 56 64    CD CD CD CD    CD CD CD CD    CD CD CD CD     2.Vd.... ........
  20.     CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
  21.     CD CD CD CD    CD CD CD CD                                   ........ ........
  22. Visual Leak Detector detected 1 memory leak (76 bytes).
  23. Largest number used: 76 bytes.
  24. Total allocations: 76 bytes.
  25. Visual Leak Detector is now exiting.
复制代码
输出与 [2. 有一处内存泄漏时的输出报告(int 型)](#2. 有一处内存泄漏时的输出报告(int 型)) 基本类似,这里提几个不同点:
第 4~5 行中,40 bytes 与代码请求的内存量大小相同,即 sizeof(int) * 10 = 40。
第 8 行,表示 operator new[]() 函数中分配内存的代码位于 new_array.cpp 文件的第 15 行,这与前面的 operator new() 函数及 new_scalar.cpp 文件不同,实际使用时可以根据这一点来判断泄漏形式,是数组还是标量
第 19~21 行,十六进制数 32 89 56 64 的十进制表示为 50 137 86 100,其中 50 86 100 对应的 ASCII 字符分别为 2、 V、 d,是可以输出显示的字符,但 137 超过了 127,不属于 ASCII 标准字符集,属于 ASCII 扩展字符集,无法直接在界面上显示,因此仍以 "." 英文句点来代替。此外,未初始化内存单个字节的值都为 CD,对应的十进制数为 205,这是 Microsoft's C++ debugging runtime library 自动初始化的结果。通常,在 Debug 模式下,MSVC 会把未初始化的栈内存全部填充成 0xCC ,当成字符串看就是”烫烫烫烫……“;同时会把未初始化的堆内存全部填充成 0xCD ,当成字符串看就是“屯屯屯屯……”。实际使用时可以根据这一点来判断是否对泄漏内存赋了初始值。关于 0xCD 这一特殊十六进制数,更详细的可以查看以下资料,国内可能无法直接访问:
第 24~26 行中,76 bytes 包含有申请 int[10] 的 40 bytes,和管理头占用的 36 bytes。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小小小幸运

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表