【Visual Leak Detector】库的 22 个 API 使用说明

2023-05-20,,

说明

使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇主要介绍 VLD 库提供的 22 个外部接口。同系列文章目录可见 《内存泄漏检测工具》目录

目录
说明
1. 头文件简介
2. 文件 vld_def.h 简介
3. 文件 vld.h 简介
3.1 接口 VLDDisable
3.2 接口 VLDEnable
3.3 接口 VLDRestore
3.4 接口 VLDGlobalDisable
3.5 接口 VLDGlobalEnable
3.6 接口 VLDReportLeaks
3.7 接口 VLDReportThreadLeaks
3.8 接口 VLDGetLeaksCount
3.9 接口 VLDGetThreadLeaksCount
3.10 接口 VLDMarkAllLeaksAsReported
3.11 接口 VLDMarkThreadLeaksAsReported
3.12 接口 VLDRefreshModules
3.13 接口 VLDEnableModule
3.14 接口 VLDDisableModule
3.15 接口 VLDGetOptions
3.16 接口 VLDGetReportFilename
3.17 接口 VLDSetOptions
3.18 接口 VLDSetModulesList
3.19 接口 VLDGetModulesList
3.20 接口 VLDSetReportOptions
3.21 接口 VLDSetReportHook
3.22 接口 VLDResolveCallstacks
4. 接口使用思路

1. 头文件简介

VLD 2.5.1 安装 完成后,安装目录的 include 文件夹下有两个头文件:vld.hvld_def.h,其中 vld.h 文件会 #include "vld_def.h",因此在实际使用时,项目中要同时添加这两个头文件(或将这两个文件放在编译器的搜索路径中),但只需包含 vld.h 文件。

2. 文件 vld_def.h 简介

这个文件里主要以宏的形式定义了 15 个 VLD 配置项的掩码,这 15 个配置项与 vld.ini 配置文件中的 14 个配置项不是完全对应的,将这些配置项掩码与 vld.h 文件中的接口结合起来用,可以实现在运行过程中对 VLD 的配置进行动态修改。其中 9 个配置项可作为接口 VLDSetOptions 的输入,另外 4 个配置项可作为接口 VLDSetReportOptions 的输入,剩余的 2 个配置项分别是 VLD_OPT_SELF_TESTVLD_OPT_VLDOFF,其中 VLD_OPT_SELF_TEST 只能通过修改 vld.ini 文件中的 SelfTest 进行设置(详见 配置项 SelfTest),VLD_OPT_VLDOFF 只能通过修改 vld.ini 文件中的 VLD 进行设置(详见 配置项 VLD),当 VLD_OPT_VLDOFF 被设置后,所有接口也都会变得不可用。

#define VLD_OPT_AGGREGATE_DUPLICATES    0x0001 //   If set, aggregate duplicate leaks in the leak report.
#define VLD_OPT_MODULE_LIST_INCLUDE 0x0002 // If set, modules in the module list are included, all others are excluded.
#define VLD_OPT_REPORT_TO_DEBUGGER 0x0004 // If set, the memory leak report is sent to the debugger.
#define VLD_OPT_REPORT_TO_FILE 0x0008 // If set, the memory leak report is sent to a file.
#define VLD_OPT_SAFE_STACK_WALK 0x0010 // If set, the stack is walked using the "safe" method (StackWalk64).
#define VLD_OPT_SELF_TEST 0x0020 // If set, perform a self-test to verify memory leak self-checking.
#define VLD_OPT_SLOW_DEBUGGER_DUMP 0x0040 // If set, inserts a slight delay between sending output to the debugger.
#define VLD_OPT_START_DISABLED 0x0080 // If set, memory leak detection will initially disabled.
#define VLD_OPT_TRACE_INTERNAL_FRAMES 0x0100 // If set, include useless frames (e.g. internal to VLD) in call stacks.
#define VLD_OPT_UNICODE_REPORT 0x0200 // If set, the leak report will be encoded UTF-16 instead of ASCII.
#define VLD_OPT_VLDOFF 0x0400 // If set, VLD will be completely deactivated. It will not attach to any modules.
#define VLD_OPT_REPORT_TO_STDOUT 0x0800 // If set, the memory leak report is sent to stdout.
#define VLD_OPT_SKIP_HEAPFREE_LEAKS 0x1000 // If set, VLD skip HeapFree memory leaks.
#define VLD_OPT_VALIDATE_HEAPFREE 0x2000 // If set, VLD verifies and reports heap consistency for HeapFree calls.
#define VLD_OPT_SKIP_CRTSTARTUP_LEAKS 0x4000 // If set, VLD skip crt srtartup memory leaks.

3. 文件 vld.h 简介

这个文件里主要声明了 VLD 的 22 个外部接口。文件前面的编译条件 defined _DEBUG || defined VLD_FORCE_ENABLE 表明 VLD 通常只能在 DEBUG 模式下运行,若要在 RELEASE 模式下运行,可以在包含头文件 vld.h 前预先定义 VLD_FORCE_ENABLE 宏。一个有趣的现象是,这个 _DEBUG 宏有时候在 RELEASE 模式下也会被定义,参考文章 神秘的 _DEBUG 宏从何处来?。接口中的一些类型别名定义如下:

typedef int            VLD_BOOL;
typedef unsigned int VLD_UINT;
typedef size_t VLD_SIZET;
typedef void* VLD_HMODULE;

3.1 接口 VLDDisable

该函数用于禁用当前线程的内存泄漏检测功能。

// VLDDisable - Disables Visual Leak Detector's memory leak detection at
// runtime. If memory leak detection is already disabled, then calling this
// function has no effect.
//
// Note: In multithreaded programs, this function operates on a per-thread
// basis. In other words, if you call this function from one thread, then
// memory leak detection is only disabled for that thread. If memory leak
// detection is enabled for other threads, then it will remain enabled for
// those other threads. It was designed to work this way to insulate you,
// the programmer, from having to ensure thread synchronization when calling
// VLDEnable() and VLDDisable(). Without this, calling these two functions
// unsynchronized could result in unpredictable and unintended behavior.
// But this also means that if you want to disable memory leak detection
// process-wide, then you need to call this function from every thread in
// the process.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDDisable ();

3.2 接口 VLDEnable

该函数用于启用当前线程的内存泄漏检测功能。

// VLDEnable - Enables Visual Leak Detector's memory leak detection at runtime.
// If memory leak detection is already enabled, which it is by default, then
// calling this function has no effect.
//
// Note: In multithreaded programs, this function operates on a per-thread
// basis. In other words, if you call this function from one thread, then
// memory leak detection is only enabled for that thread. If memory leak
// detection is disabled for other threads, then it will remain disabled for
// those other threads. It was designed to work this way to insulate you,
// the programmer, from having to ensure thread synchronization when calling
// VLDEnable() and VLDDisable(). Without this, calling these two functions
// unsynchronized could result in unpredictable and unintended behavior.
// But this also means that if you want to enable memory leak detection
// process-wide, then you need to call this function from every thread in
// the process.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDEnable ();

3.3 接口 VLDRestore

该函数用于还原当前线程内存泄漏检测功能的开关状态。

// VLDRestore - Restore Visual Leak Detector's previous state.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDRestore ();

为了便于理解,下面贴出这一接口对应的源码(详见 vld.cpp 第 2548~2561 行):

void VisualLeakDetector::RestoreLeakDetectionState ()
{
tls_t *tls; if (m_options & VLD_OPT_VLDOFF) {
// VLD has been turned off.
return;
} // Restore state memory leak detection for the current thread.
tls = getTls();
tls->flags &= ~(VLD_TLS_DISABLED | VLD_TLS_ENABLED);
tls->flags |= tls->oldFlags & (VLD_TLS_DISABLED | VLD_TLS_ENABLED);
}

3.4 接口 VLDGlobalDisable

该函数用于全局禁用 VLD 功能。

// VLDGlobalDisable - Disables Visual Leak Detector's memory leak detection at
// runtime in all threads. If memory leak detection is already disabled,
// then calling this function has no effect.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDGlobalDisable ();

3.5 接口 VLDGlobalEnable

该函数用于全局启用 VLD 功能。

// VLDGlobalEnable - Enables Visual Leak Detector's memory leak detection
// at runtime in all threads. If memory leak detection is already enabled,
// which it is by default, then calling this function has no effect.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDGlobalEnable ();

3.6 接口 VLDReportLeaks

该函数用于打印整个程序当前执行此函数前的内存泄漏报告。由于在程序关闭后 VLD 会自动输出报告,因此若不需要在程序运行过程中输出报告,就不需使用此函数。

// VLDReportLeaks - Report leaks up to the execution point.
//
// Return Value:
//
// None.
//
__declspec(dllimport) VLD_UINT VLDReportLeaks ();

3.7 接口 VLDReportThreadLeaks

该函数用于打印指定线程在执行此函数前的内存泄漏报告。

// VLDReportThreadLeaks - Report thread leaks up to the execution point.
//
// threadId: thread Id.
//
// Return Value:
//
// None.
//
__declspec(dllimport) VLD_UINT VLDReportThreadLeaks (VLD_UINT threadId);

3.8 接口 VLDGetLeaksCount

该函数用于获取整个程序当前的内存泄漏数量。

// VLDGetLeaksCount - Return memory leaks count to the execution point.
//
// Return Value:
//
// None.
//
__declspec(dllimport) VLD_UINT VLDGetLeaksCount ();

3.9 接口 VLDGetThreadLeaksCount

该函数用于获取指定线程当前的内存泄漏数量。

// VLDGetThreadLeaksCount - Return thread memory leaks count to the execution point.
//
// threadId: thread Id.
//
// Return Value:
//
// None.
//
__declspec(dllimport) VLD_UINT VLDGetThreadLeaksCount (VLD_UINT threadId);

3.10 接口 VLDMarkAllLeaksAsReported

该函数用于标记当前的泄漏为已经报告过,后续不再报告。

// VLDMarkAllLeaksAsReported - Mark all leaks as reported.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDMarkAllLeaksAsReported ();

3.11 接口 VLDMarkThreadLeaksAsReported

该函数用于标记指定线程当前的泄漏为已经报告过,后续不再报告。

// VLDMarkThreadLeaksAsReported - Mark thread leaks as reported.
//
// threadId: thread Id.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDMarkThreadLeaksAsReported (VLD_UINT threadId);

3.12 接口 VLDRefreshModules

该函数用于刷新加载的模块列表,以便针对动态加载的模块进行内存泄漏检查。

// VLDRefreshModules - Look for recently loaded DLLs and patch them if necessary.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDRefreshModules();

3.13 接口 VLDEnableModule

该函数用于对指定模块启用内存泄漏检测。输入参数为指定模块(dll 或者 exe)的句柄,可以通过调用 Win32API 来获得(常用的有 GetModuleHandleW 函数、LoadLibraryA 函数)。

// VLDEnableModule - Enable Memory leak checking on the specified module.
//
// module: module handle.
//
// Return Value:
//
// None.
// __declspec(dllimport) void VLDEnableModule(VLD_HMODULE module);

例如,若想检测一个已载入当前进程的动态链接库 test.dll 是否存在内存泄漏,可以按以下方式使用该接口(需包含头文件 Windows.h,若为动态加载的模块,还需额外使用 VLDRefreshModules() 刷新内部的模块列表):

// 调用 Win32 API 获得模块句柄
HMODULE h_dll = GetModuleHandleW(L"test.dll"); // 对该模块启用 VLD 功能
VLDEnableModule(h_dll); // 调用模块中的函数
...

3.14 接口 VLDDisableModule

该函数用于对指定模块禁用内存泄漏检测。与 VLDEnableModule 相对应,输入参数为指定模块(dll 或者 exe)的句柄,可以通过调用 Win32API 来获得。

// VLDDisableModule - Disable Memory leak checking on the specified module.
//
// module: module handle.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDDisableModule(VLD_HMODULE module);

3.15 接口 VLDGetOptions

该函数用于获取当前的配置掩码值(与 VLDSetOptions 相对应),将掩码值结合 VLDSetOptions9 个配置掩码宏可以人工推断出 VLD 当前的配置。

// VLDGetOptions - Return all current options.
//
// Return Value:
//
// Mask of current options.
//
__declspec(dllimport) VLD_UINT VLDGetOptions();

例如,若先前使用 VLDSetOptions 接口对 VLD 做了以下配置:

VLDSetOptions(VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS, 256, 64);

VLDGetOptions() 的返回值为 4097,计算方式如下:

VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS == 0x1001 == 4097

若调用此函数前,未使用 VLDSetOptions 接口更改配置,且未修改 vld.ini 配置文件,则该函数的返回值为默认的 16386,即 0x4002

3.16 接口 VLDGetReportFilename

该函数用于获取生成的泄漏报告文件路径。注意:用于存储文件路径的 wchar_t 数组 filename 需预分配足够大的内存,以防出现意外的情况,MAX_PATHwindows 系统下文件路径的最大长度(详见 MAX_PATH,值为 260)。

// VLDGetReportFilename - Return current report filename.
//
// filename: current report filename (max characters - MAX_PATH).
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDGetReportFilename(wchar_t *filename);

3.17 接口 VLDSetOptions

该函数用于设置配置掩码值(由相应的配置掩码宏通过逻辑运算得到)、设置为每个泄漏块数据转储的最大字节数(参考 配置项 MaxDataDump)、设置对每个泄漏块进行堆栈跟踪的最大帧数(参考 配置项 MaxTraceFrames)。

// VLDSetOptions - Update the report options via function call rather than INI file.
//
// option_mask: Only the following flags are checked
// VLD_OPT_AGGREGATE_DUPLICATES
// VLD_OPT_MODULE_LIST_INCLUDE
// VLD_OPT_SAFE_STACK_WALK
// VLD_OPT_SLOW_DEBUGGER_DUMP
// VLD_OPT_TRACE_INTERNAL_FRAMES
// VLD_OPT_START_DISABLED
// VLD_OPT_SKIP_HEAPFREE_LEAKS
// VLD_OPT_VALIDATE_HEAPFREE
//
// maxDataDump: maximum number of user-data bytes to dump for each leaked block.
//
// maxTraceFrames: maximum number of frames per stack trace for each leaked block.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDSetOptions(VLD_UINT option_mask, VLD_SIZET maxDataDump, VLD_UINT maxTraceFrames);

查看该接口的 源码 可知,第一个参数的合法输入为以下 9 个配置掩码宏之一(上面的接口说明并不全面)、或者它们的逻辑或(|)组合:

// option_mask 的合法输入:以下 9 个配置宏之一
#define VLD_OPT_AGGREGATE_DUPLICATES 0x0001 // If set, aggregate duplicate leaks in the leak report.
#define VLD_OPT_MODULE_LIST_INCLUDE 0x0002 // If set, modules in the module list are included, all others are excluded.
#define VLD_OPT_SAFE_STACK_WALK 0x0010 // If set, the stack is walked using the "safe" method (StackWalk64).
#define VLD_OPT_SLOW_DEBUGGER_DUMP 0x0040 // If set, inserts a slight delay between sending output to the debugger.
#define VLD_OPT_START_DISABLED 0x0080 // If set, memory leak detection will initially disabled.
#define VLD_OPT_TRACE_INTERNAL_FRAMES 0x0100 // If set, include useless frames (e.g. internal to VLD) in call stacks.
#define VLD_OPT_SKIP_HEAPFREE_LEAKS 0x1000 // If set, VLD skip HeapFree memory leaks.
#define VLD_OPT_VALIDATE_HEAPFREE 0x2000 // If set, VLD verifies and reports heap consistency for HeapFree calls.
#define VLD_OPT_SKIP_CRTSTARTUP_LEAKS 0x4000 // If set, VLD skip crt srtartup memory leaks.

例如,我想同时开启 VLD_OPT_AGGREGATE_DUPLICATESVLD_OPT_SKIP_HEAPFREE_LEAKS,并设置 maxDataDump=256maxTraceFrames=64,可以用以下几种方式传参,它们的效果是一样的:

VLDSetOptions(VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS, 256, 64);
VLDSetOptions(0x1001, 256, 64);
VLDSetOptions(4097, 256, 64);

需要注意的是,每次使用此函数时,内部都会先将这 9 个配置置零,然后使用新输入的配置,因此,若输入不合法,就会丢失对应 bit 处上一次的配置状态,具体细节可查看源码。

3.18 接口 VLDSetModulesList

该函数用于设置要包含或者排除泄漏检测的模块列表。阅读 源码 可知,第一个参数 modules 的最大有效长度为 512

// VLDSetModulesList - Set list of modules included/excluded in leak detection
// depending on parameter "includeModules".
//
// modules: list of modules to be forcefully included/excluded in leak detection.
//
// includeModules: include or exclude that modules.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDSetModulesList(const wchar_t *modules, VLD_BOOL includeModules);

源码(详见 vld.cpp 第 867~882 行)中判断某个模块 modulename 是否在所给列表 m_forcedModuleList 中的核心代码如下,由于使用的是 wcsstr 函数 进行字符串比较,因此模块名之间可用任意字符或字符串进行分隔,且不区分大小写,这与配置文件 vld.ini 中的 ForceIncludeModules 配置项一样(详见 配置项 ForceIncludeModules)。另外,从源码中也可以看出,当第二个参数为 false 时,将执行后面的判断语句(条件为 wcsstr(m_forcedModuleList, modulename) != NULL),意味着不在列表中的模块都会被开启内存检测,这可能会导致很多误判,被大量的伪泄漏刷屏,因此需慎传 false

// This module does not import VLD. This means that none of the module's
// sources #included vld.h.
if ((m_options & VLD_OPT_MODULE_LIST_INCLUDE) != 0)
{
if (wcsstr(m_forcedModuleList, modulename) == NULL) {
// Exclude this module from leak detection.
moduleFlags |= VLD_MODULE_EXCLUDED;
}
}
else
{
if (wcsstr(m_forcedModuleList, modulename) != NULL) {
// Exclude this module from leak detection.
moduleFlags |= VLD_MODULE_EXCLUDED;
}
}

QT 中实际使用时(测试环境:QT 5.9.2MSVC 2015 32bitDebug 模式,VLD 版本为 2.5.1),发现这个函数好像只能修改内部的 m_forcedModuleList 列表,并没有按照第二个参数 includeModules 立马更新指定模块的检测状态,即使接着使用 VLDRefreshModules() 函数进行刷新,也没有实现按新列表进行检测的效果。因此,要实现对某个 DLL 库的动态检测控制,最好还是使用 VLDEnableModuleVLDDisableModuleVLDRefreshModules 这三个接口。若没有动态检测控制的需求,则可以修改配置文件 vld.ini 中的 ForceIncludeModules 配置项(详见 配置项 ForceIncludeModules),修改配置文件是没有这个问题的。

3.19 接口 VLDGetModulesList

该函数用于获取检测中的模块列表。获取的模块列表是由 VLDSetModulesList 设置的,也可能是由 vld.ini 文件中的 ForceIncludeModules 设置的,用于存储模块列表的 wchar_t 数组 modules 需预分配足够大的内存(通常为 512),以防出现意外的情况。第二个参数 size 为想要获取的字符串长度,一般设置为第一个参数 modules 的长度(通常为 512)。

// VLDGetModulesList - Return current list of included/excluded modules
// depending on flag VLD_OPT_TRACE_INTERNAL_FRAMES.
//
// modules: destination string for list of included/excluded modules (maximum length 512 characters).
//
// size: maximum string size.
//
// Return Value:
//
// VLD_BOOL: TRUE if include modules, otherwise FALSE.
//
__declspec(dllimport) VLD_BOOL VLDGetModulesList(wchar_t *modules, VLD_UINT size);

3.20 接口 VLDSetReportOptions

该函数用于设置泄漏报告输出方式。

// VLDSetReportOptions - Update the report options via function call rather than INI file.
//
// Only the following flags are checked
// VLD_OPT_REPORT_TO_DEBUGGER
// VLD_OPT_REPORT_TO_FILE
// VLD_OPT_REPORT_TO_STDOUT
// VLD_OPT_UNICODE_REPORT
//
// filename is optional and can be NULL.
//
// Return Value:
//
// None.
//
__declspec(dllimport) void VLDSetReportOptions(VLD_UINT option_mask, const wchar_t *filename);

第一个参数的合法输入为以下 4 个配置掩码宏之一、或者它们的逻辑或(|)组合,第二个参数可以为 NULL 但不能省略。

// option_mask 的合法输入:以下 4 个配置宏之一
#define VLD_OPT_REPORT_TO_DEBUGGER 0x0004 // If set, the memory leak report is sent to the debugger.
#define VLD_OPT_REPORT_TO_FILE 0x0008 // If set, the memory leak report is sent to a file.
#define VLD_OPT_REPORT_TO_STDOUT 0x0800 // If set, the memory leak report is sent to stdout.
#define VLD_OPT_UNICODE_REPORT 0x0200 // If set, the leak report will be encoded UTF-16 instead of ASCII.

需要注意的是,如果设置了 VLD_OPT_UNICODE_REPORT,即使没设置 VLD_OPT_REPORT_TO_FILE,泄漏报告也会输出到文件,这与 vld.ini 文件中的 配置项 ReportEncoding 效果一样。阅读 源码 可知,若设置了 VLD_OPT_REPORT_TO_STDOUT,程序会调用 fputs(messagea, stdout) 输出泄漏报告至标准输出窗,若设置了 VLD_OPT_REPORT_TO_DEBUGGER,程序会调用 OutputDebugStringW(messagew) 输出泄露报告至调试窗(详见 OutputDebugStringW 函数)。

3.21 接口 VLDSetReportHook

该函数用于自定义内存泄漏报告回调函数,与 CrtSetReportHook 函数 很相似。

// VLDSetReportHook - Installs or uninstalls a client-defined reporting function by hooking it
// into the C run-time debug reporting process (debug version only).
//
// mode: The action to take: VLD_RPTHOOK_INSTALL or VLD_RPTHOOK_REMOVE.
//
// pfnNewHook: Report hook to install or remove.
//
// Return Value:
//
// int: 0 if success.
//
__declspec(dllimport) int VLDSetReportHook(int mode, VLD_REPORT_HOOK pfnNewHook);

其中的 VLD_REPORT_HOOKvld_def.h 中有给出别名声明,自定义的报告函数必须是以下类型的函数。

typedef int (__cdecl * VLD_REPORT_HOOK)(int reportType, wchar_t *message, int *returnValue);

其中的 VLD_RPTHOOK_INSTALLVLD_RPTHOOK_REMOVE 也在 vld_def.h 中有给出宏定义,安装自定义报告函数时传 VLD_RPTHOOK_INSTALL,卸载时传 VLD_RPTHOOK_INSTALL,若设置成功,VLDSetReportHook 返回 0,否则返回 -1

#define VLD_RPTHOOK_INSTALL  0
#define VLD_RPTHOOK_REMOVE 1

查看 API 源码(详见 utility.cpp 第 674~774 行),可以得到以下几点信息:

自定义报告函数的返回值为 true(非零)时,其后不会调用默认的报告输出函数,返回值为 false(零)时,其后仍会调用默认的报告输出函数。
VLD 传递给自定义报告函数的第一个参数 reportType 值恒为 0,可以不使用这个值;
第二个参数是每次检测到泄漏时默认的输出信息,可以对此做一个字符串过滤,只输出想要的信息(可使用 OutputDebugStringWfputs 进行打印,其他输出函数可能无法正常使用,因为 VLD 源码未包含对应头文件);
第三个参数是自定义报告函数传递给 VLD 的值,当 *returnValue = 1 时,会触发 __debugbreak() 函数(详见 __debugbreak 函数),将在代码中引起断点,并在其中提示用户运行调试程序,当 *returnValue != 1 时,无任何影响。

例如,我定义了以下自定义报告函数:

int MyReportHook(int, wchar_t* message, int* returnValue)
{
// 不让VLD触发__debugbreak()函数
*returnValue = 0; // 使用默认的 Block 信息
if (wcsstr(message, L"Block") != NULL) {
return false;
} // 使用自定义的退出信息
if (wcsstr(message, L"Visual Leak Detector is now exiting") != NULL) {
wchar_t wcs[512]{};
wcscpy (wcs, L"This is a custom output: ");
wcscat (wcs, message);
OutputDebugStringW(wcs);
return true;
} // 忽略其他信息
return true;
}

然后在 main 主函数的开头添加上:

VLDSetReportHook(VLD_RPTHOOK_INSTALL, MyReportHook);

DEBUG 模式下运行程序,程序结束后得到以下结果:

Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
Visual Leak Detector Version 2.5.1 installed.
---------- Block 2 at 0x015D2418: 40 bytes ----------
---------- Block 1 at 0x015D27E0: 40 bytes ----------
This is a custom output: Visual Leak Detector is now exiting.

未使用自定义报告函数时(或者紧接着使用 VLD_RPTHOOK_REMOVE 卸载 MyReportHook)的输出为:

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 2 at 0x00964198: 40 bytes ----------
Leak Hash: 0x01EF1389, Count: 1, Total 40 bytes
Call Stack (TID 10072):
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 (60): testVLD.exe!main() + 0x7 bytes
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:
CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........
CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........
CD CD CD CD CD CD CD CD ........ ........ ---------- Block 1 at 0x00964248: 40 bytes ----------
Leak Hash: 0x5D0E4327, Count: 1, Total 40 bytes
Call Stack (TID 10072):
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 (59): testVLD.exe!main() + 0x7 bytes
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:
CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........
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 2 memory leaks (152 bytes).
Largest number used: 152 bytes.
Total allocations: 152 bytes.
Visual Leak Detector is now exiting.

3.22 接口 VLDResolveCallstacks

该函数用于对所有已跟踪的调用堆栈进行符号解析,当调用堆栈中存在已卸载的模块函数时,可以调用此函数重新整理调用堆栈中的符号信息(函数名、行号、指令偏移量等信息)。

// VLDResolveCallstacks - Performs symbol resolution for all saved extent CallStack's that have
// been tracked by Visual Leak Detector. This function is necessary for applications that
// dynamically load and unload modules, and through which memory leaks might be included.
// If this is NOT called, stack traces may have stack frames with no symbol information. This
// happens because the symbol API's cannot look up symbols for a binary / module that has been unloaded
// from the process.
//
// Return Value:
//
// int: 0 if successfully resolved all callstacks.
//
__declspec(dllexport) int VLDResolveCallstacks();

按编码规范来说,这个接口前面应该是 __declspec(dllimport),但它实际却是 __declspec(dllexport),这种用法不知是库作者笔误,还是别有用途,有兴趣的可以深究下去。

4. 接口使用思路

使用 VLDDisableVLDEnableVLDRestore 可以做到只检测指定线程的内存泄漏,或者排除指定线程的内存泄漏检测。
使用 VLDGlobalDisableVLDGlobalEnable 可以做到只检测特定时间阶段的内存泄漏,比如只检测程序完成某项任务期间的内存泄漏,而不检测其他时间段的内存泄漏。
使用 VLDEnableModuleVLDDisableModuleVLDSetModulesListVLDGetModulesListVLDRefreshModulesVLDResolveCallstacks 可以实现对指定模块进行内存检测的动态控制。
使用 VLDReportLeaksVLDReportThreadLeaksVLDMarkAllLeaksAsReportedVLDMarkThreadLeaksAsReportedVLDSetReportOptionsVLDSetReportHookVLDGetReportFilename 可以实现对泄漏报告的动态定制。
使用 VLDGetLeaksCountVLDGetThreadLeaksCount 可以实现对泄漏信息的实时获取。
使用 VLDGetOptionsVLDSetOptions 可以实现运行过程中动态修改 VLD 的配置。

【Visual Leak Detector】库的 22 个 API 使用说明的相关教程结束。

《【Visual Leak Detector】库的 22 个 API 使用说明.doc》

下载本文的Word格式文档,以方便收藏与打印。