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

来源:https://www.cnblogs.com/young520/archive/2023/03/26/17259449.html
-Advertisement-
Play Games

使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹在 QT 中使用 VLD 時,有一處記憶體泄漏時的輸出報告解析。 ...


說明

使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。

目錄


1. 使用方式

在 QT 中使用 VLD 的方法可以查看另外幾篇博客:

本次測試使用的環境為:QT 5.9.2MSVC 2015 32bitDebug 模式,VLD 版本為 2.5.1,VLD 配置文件不做任何更改使用預設配置,測試工程所在路徑為:E:\Cworkspace\Qt 5.9\QtDemo\testVLD

2. 有一處記憶體泄漏時的輸出報告(int 型)

寫一個有一處記憶體泄漏的程式,如下:

#include <QCoreApplication>
#include "vld.h"

void testFun()
{
    int *ptr = new int(0x55345678);
    printf("ptr = %08x, *ptr = %08x", ptr, *ptr);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    testFun();

    return a.exec();
}

程式運行時,在標準輸出窗會輸出以下結果:

ptr = 0127b7a0, *ptr = 55345678

程式運行結束後,檢測到了記憶體泄漏,VLD 會輸出以下報告(本例中出現一處記憶體泄漏),第 1~3 行顯示 VLD 運行狀態,第 4~21 行顯示泄漏記憶體的詳細信息,第 22~24 行總結此次泄漏情況,第 25 行顯示 VLD 退出狀態。

Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
Visual Leak Detector Version 2.5.1 installed.
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x0127B7A0: 4 bytes ----------
  Leak Hash: 0xEB4D3A14, Count: 1, Total 4 bytes
  Call Stack (TID 22408):
    ucrtbased.dll!malloc()
    f:\dd\vctools\crt\vcstartup\src\heap\new_scalar.cpp (19): testVLD.exe!operator new() + 0x9 bytes
    e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (6): testVLD.exe!testFun() + 0x7 bytes
    e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (16): testVLD.exe!main()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
    KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  Data:
    78 56 34 55                                                  xV4U.... ........


Visual Leak Detector detected 1 memory leak (40 bytes).
Largest number used: 40 bytes.
Total allocations: 40 bytes.
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) 表示產生此記憶體泄漏塊函數所線上程的 TID22408,據此來指示發生記憶體泄漏的線程。在調試多線程程式時,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 bytes0xEE bytes),這也是一個 Windows 操作系統函數,用於檢索與進程相關的應用程式容器名稱,由 Windows 系統的各個部分和其他需要知道應用程式容器名稱的程式調用,關於 Windows 容器的介紹,可以查看 Microsoft Windows 和容器

第 18~19 行,分別用十六進位及 ASCII 字元顯示了泄漏記憶體塊中信息,記憶體初始化時賦的初始值為 0x55345678,電腦預設使用小端位元組序,因此在記憶體中各位元組的十六進位初始值分別為 78 56 34 55,轉化為十進位數,0x780x560x340x55 分別為 120、86、52、85,查找 ASCII 碼表,可得這四個位元組對應的 ASCII 字元分別為 xV4U,與 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,即代碼申請 int4 bytes,和管理頭占用的 36 bytes

第 25 行,表示 VLD 正常退出。

在程式的第 6 行加個斷點,按 F5 進入調試狀態,結果如下,調用堆棧中各函數的名稱、所屬文件、所在行號、調用順序都和 VLD 一致。

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關註“木三百川”

3. 有一處記憶體泄漏時的輸出報告(int 數組型)

寫一個有一處記憶體泄漏的程式,如下:

#include <QCoreApplication>
#include "vld.h"

void testFun()
{
    int *ptr = new int[10];
    ptr[0] = 0x64568932;
    printf("ptr = %08x", ptr);
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    testFun();

    return a.exec();
}

程式運行時,在標準輸出窗會輸出以下結果:

ptr = 00ab4340

程式運行結束後,檢測到了記憶體泄漏,VLD 會輸出以下報告(本例中出現一處記憶體泄漏),第 1~3 行顯示 VLD 運行狀態,第 4~23 行顯示泄漏記憶體的詳細信息,第 24~26 行總結此次泄漏情況,第 27 行顯示 VLD 退出狀態。

Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
Visual Leak Detector Version 2.5.1 installed.
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x00AB4340: 40 bytes ----------
  Leak Hash: 0x39CB72AB, Count: 1, Total 40 bytes
  Call Stack (TID 29256):
    ucrtbased.dll!malloc()
    f:\dd\vctools\crt\vcstartup\src\heap\new_array.cpp (15): testVLD.exe!operator new[]() + 0x9 bytes
    e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (6): testVLD.exe!testFun() + 0x7 bytes
    e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (17): testVLD.exe!main()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
    f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
    f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
    KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  Data:
    32 89 56 64    CD CD CD CD    CD CD CD CD    CD CD CD CD     2.Vd.... ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD                                   ........ ........


Visual Leak Detector detected 1 memory leak (76 bytes).
Largest number used: 76 bytes.
Total allocations: 76 bytes.
Visual Leak Detector is now exiting.

輸出與上一節基本類似,這裡提幾個不同點:

第 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

本文作者:木三百川

本文鏈接:https://www.cnblogs.com/young520/p/17259449.html

版權聲明:本文系博主原創文章,著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請附上出處鏈接。遵循 署名-非商業性使用-相同方式共用 4.0 國際版 (CC BY-NC-SA 4.0) 版權協議。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 代理模式(Proxy Pattern)是一種結構型設計模式,結構型模式描述如何將類或對象按某種佈局組成更大的結構。它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象擁有與實際對象相同的介面,因此它可以被用來代替實際對象。 ...
  • 1. JVM線程優化 1.1. 當空間不足時,可以調整線程使用的記憶體 1.2. 每個線程都有一個原生棧,操作系統會在這裡存儲線程的調用棧信息 1.3. 原生棧的大小是1 MB 1.3.1. 32位的Windows JVM原生棧大小是320KB 1.3.2. 在64位的JVM中,通常不會修改這個值 1 ...
  • 1. 下載jsoncpp -->https://github.com/open-source-parsers/jsoncpp/tree/update 兩種下載方法: 方法一:git clone ... 到伺服器上(或虛擬機上...),有點慢,甚至會失敗...! 方法二:下載 zip包,通過第三方軟體 ...
  • 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹在 QT 中使用 VLD 時,有多處記憶體泄漏時的輸出報告解析。 ...
  • 將用戶發來的指令以RESP協議的形式存儲在本地的AOF文件,重啟Redis後執行此文件恢複數據 https://github.com/csgopher/go-redis 本文涉及以下文件: redis.conf:配置文件 aof:實現aof redis.conf appendonly yes app ...
  • 原文鏈接: Go 語言 new 和 make 關鍵字的區別 本篇文章來介紹一道非常常見的面試題,到底有多常見呢?可能很多面試的開場白就是由此開始的。那就是 new 和 make 這兩個內置函數的區別。 其實這個問題本身並不複雜,簡單來說就是,new 只分配記憶體,而 make 只能用於 slice、m ...
  • 南昌航空大學-軟體學院-22206104-段清如-JAVA第一次Blog作業 前言: 這個學期才開始接觸java,到現在一個多月的時間,已經差不多可以寫出一些基本的簡單的程式了。對比上個學期學習的C語言,我認為java更加方便,方法更多,函數更多,但是時間效率上略遜一籌。在這一個月的java學習過程 ...
  • 在上一章中已經看到,odoo能夠為給定模型生成預設視圖。實際上,預設視圖對於業務應用程式來說是不可接受的。相反,我們至少應該以邏輯的方式組織各個欄位。 視圖是在帶有操作和菜單的XML文件中定義的。它們是ir.ui.view model的實例。 在我們的estate模塊中,我們需要以邏輯方式組織欄位: ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...