说明

使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇对 VLD 1.0 源码做内存泄漏检测的思路进行剖析。同系列文章目录可见 《内存泄漏检测工具》目录

1. 源码获取

version 1.0 及之前版本都使用旧的检测思路,可以在网站 CodeProject-Visual-Leak-Detector 中下载 version 1.0 的源码(国内网络资源:百度网盘-vld-1.0 源码包),同时在该网站中可以看到库作者 Dan Moulding 对旧检测原理的介绍。这个网站中有下图这段文字,但经过我一番查找,还是未找到 Dan Moulding 对后续新检测原理的介绍文章,本篇文章主要对 version 1.0 的源码进行剖析。

version 1.0 的源码算上注释一共不到 3000 行,而且代码注释写得很详细,推荐有兴趣的仔细阅读源码。以下资料可能对理解其检测原理有帮助:

2. 源码文件概览

version 1.0 源码包中一共有 11 个文件,目录结构如下:

vld-10-src
CHANGES.txt
COPYING.txt
README.html
vld.cpp
vld.dsp
vld.h
vldapi.cpp
vldapi.h
vldint.h
vldutil.cpp
vldutil.h

其中 3.cpp 文件,4.h 文件,2.txt 文件,1.dsp 文件,1.html 文件,各文件用途简述如下:

  • 文件 README.html 为网页版的帮助文档,里面介绍了 VLD 的功能、使用方法、配置选项、编译方法、功能限制等。从这个帮助文档中可以得知:这个版本的 VLD 只能检测由 newmalloc 导致的内存泄漏;若需要检测多个 DLL 库,则要确保加载这些库前,已经包含了 vld.h 头文件。

  • 文件 CHANGES.txt 为版本迭代日志,记录了各版本的更新概要;

  • 文件 COPYING.txtLGPL 2.1 开源协议;

  • 文件 vld.dspVisual C++ 的项目文件,全称 Microsoft Developer Studio Project File

  • 文件 vldapi.h 为使用 VLD 库时需包含的头文件之一,里面声明了两个接口:VLDEnable()VLDDisable()

  • 文件 vldapi.cpp 里面是接口 VLDEnable()VLDDisable() 的函数定义;

  • 文件 vldint.h 里面定义了 dbghelp.dll 中一些函数的别名,并声明了 VisualLeakDetector 类;

  • 文件 vldutil.h 里面定义了一些 VLD 内部使用的宏,重载了内部的 new/delete 运算符,并声明了 CallStack 类与 BlockMap 类,这两个类是 VLD 自定义的数据结构,用来存储泄漏信息,CallStack 类似于 STL vectorBlockMap 类似于 STL map

  • 文件 vldutil.cppCallStackBlockMap 的类方法实现;

  • 文件 vld.h 为使用 VLD 库时需包含的头文件之一,里面是一些配置选项的宏定义,用户可使用这些宏来定制 VLD 的功能。特别地,这个文件里有以下一行代码,用来强制引用 VLD 库中的全局对象 visualleakdetector,使其链接到当前程序(资料参考 MSDN-pragma-commentMSDN-Linker-optionsMSDN-/INCLUDE)。

    // Force a symbolic reference to the global VisualLeakDetector class object from
    // the library. This enusres that the object is linked with the program, even
    // though nobody directly references it outside of the library.
    #pragma comment(linker, "/include:?visualleakdetector@@3VVisualLeakDetector@@A")
  • 文件 vld.cppVisualLeakDetector 的类方法实现,主要功能的代码都在这个文件里;

3. 源码剖析

3.1 注册自定义 AllocHook 函数

使用 #pragma init_seg (compiler) 指令构造一个全局对象 visualleakdetector,来确保这个对象的构造函数最先被调用(详见 vld.cpp 第 49~55 行)。

// The one and only VisualLeakDetector object instance. This is placed in the
// "compiler" initialization area, so that it gets constructed during C runtime
// initialization and before any user global objects are constructed. Also,
// disable the warning about us using the "compiler" initialization area.
#pragma warning (disable:4074)
#pragma init_seg (compiler)
VisualLeakDetector visualleakdetector;

在全局对象 visualleakdetector 的构造函数中调用 _CrtSetAllocHook 接口注册自定义 AllocHook 函数,使程序能捕捉之后的内存操作(内存分配/内存释放)事件(详见 vld.cpp 第 57~95 行)。

// Constructor - Dynamically links with the Debug Help Library and installs the
// allocation hook function so that the C runtime's debug heap manager will
// call the hook function for every heap request.
//
VisualLeakDetector::VisualLeakDetector ()
{
... if (m_tlsindex == TLS_OUT_OF_INDEXES) {
report("ERROR: Visual Leak Detector: Couldn't allocate thread local storage.\n");
}
else if (linkdebughelplibrary()) {
// Register our allocation hook function with the debug heap.
m_poldhook = _CrtSetAllocHook(allochook);
report("Visual Leak Detector Version "VLD_VERSION" installed ("VLD_LIBTYPE").\n");
...
} report("Visual Leak Detector is NOT installed!\n");
}

此外,在 visualleakdetector 的构造函数中,还做了以下工作:

  • 初始化 VLD 的配置信息,详见 vld.cpp 第 71~75 行、第 84~90 行、以及 reportconfig() 函数,详见 vld.cpp 第 768~800 行。
  • 动态加载 dbghelp.dll 库,用于后续获取调用堆栈信息,详见 linkdebughelplibrary() 函数,vld.cpp 第 662~741 行,所使用的 dbghelp.dll 库版本为 6.3.17.0

3.2 使用 StackWalk64 获取调用堆栈信息

全局对象 visualleakdetector 有一个成员变量 m_mallocmap,用来存储堆内存分配时的调用堆栈信息,这是一种基于红黑树的自定义 Map 容器(类似于 STLmap),这个容器的声明及定义可见 vldutil.hvldutil.cpp 文件 。

////////////////////////////////////////////////////////////////////////////////
//
// The BlockMap Class
//
// This data structure is similar in concept to a STL map, but is specifically
// tailored for use by VLD, making it more efficient than a standard STL map.
//
// The purpose of the BlockMap is to map allocated memory blocks (via their
// unique allocation request numbers) to the call stacks that allocated them.
// One of the primary concerns of the BlockMap is to be able to quickly insert
// search and delete. For this reason, the underlying data structure is
// a red-black tree (a type of balanced binary tree).
//
// The red-black tree is overlayed on top of larger "chunks" of pre-allocated
// storage. These chunks, which are arranged in a linked list, make it possible
// for the map to have reserve capacity, allowing it to grow dynamically
// without incurring a heap hit each time a new element is added to the map.
//
class BlockMap
{
...
};

每次进行内存操作(alloc/realloc/free)时,都会自动执行前述自定义的 AllocHook 函数,其定义如下,详见 vld.cpp 第 175~260 行。

// allochook - This is a hook function that is installed into Microsoft's
// CRT debug heap when the VisualLeakDetector object is constructed. Any time
// an allocation, reallocation, or free is made from/to the debug heap,
// the CRT will call into this hook function.
//
// Note: The debug heap serializes calls to this function (i.e. the debug heap
// is locked prior to calling this function). So we don't need to worry about
// thread safety -- it's already taken care of for us.
//
// - type (IN): Specifies the type of request (alloc, realloc, or free).
//
// - pdata (IN): On a free allocation request, contains a pointer to the
// user data section of the memory block being freed. On alloc requests,
// this pointer will be NULL because no block has actually been allocated
// yet.
//
// - size (IN): Specifies the size (either real or requested) of the user
// data section of the memory block being freed or requested. This function
// ignores this value.
//
// - use (IN): Specifies the "use" type of the block. This can indicate the
// purpose of the block being requested. It can be for internal use by
// the CRT, it can be an application defined "client" block, or it can
// simply be a normal block. Client blocks are just normal blocks that
// have been specifically tagged by the application so that the application
// can separately keep track of the tagged blocks for debugging purposes.
//
// - request (IN): Specifies the allocation request number. This is basically
// a sequence number that is incremented for each allocation request. It
// is used to uniquely identify each allocation.
//
// - filename (IN): String containing the filename of the source line that
// initiated this request. This function ignores this value.
//
// - line (IN): Line number within the source file that initiated this request.
// This function ignores this value.
//
// Return Value:
//
// Always returns true, unless another allocation hook function was already
// installed before our hook function was called, in which case we'll return
// whatever value the other hook function returns. Returning false will
// cause the debug heap to deny the pending allocation request (this can be
// useful for simulating out of memory conditions, but Visual Leak Detector
// has no need to make use of this capability).
//
int VisualLeakDetector::allochook (int type, void *pdata, size_t size, int use, long request, const unsigned char *file, int line)
{
... // Call the appropriate handler for the type of operation.
switch (type) {
case _HOOK_ALLOC:
visualleakdetector.hookmalloc(request);
break; case _HOOK_FREE:
visualleakdetector.hookfree(pdata);
break; case _HOOK_REALLOC:
visualleakdetector.hookrealloc(pdata, request);
break; default:
visualleakdetector.report("WARNING: Visual Leak Detector: in allochook(): Unhandled allocation type (%d).\n", type);
break;
} ...
}

这个函数的输入参数中,有一个 request 值,这个值被用来做为所分配内存块的唯一标识符,即 m_mallocmapkey 值。函数体中,会根据内存操作事件的类型做对应的处理,hookmalloc()hookfree()hookrealloc() 的定义详见 vld.cpp 第 594~660 行。

void VisualLeakDetector::hookfree (const void *pdata)
{
long request = pHdr(pdata)->lRequest; m_mallocmap->erase(request);
} void VisualLeakDetector::hookmalloc (long request)
{
CallStack *callstack; if (!enabled()) {
// Memory leak detection is disabled. Don't track allocations.
return;
} callstack = m_mallocmap->insert(request);
getstacktrace(callstack);
} void VisualLeakDetector::hookrealloc (const void *pdata, long request)
{
// Do a free, then do a malloc.
hookfree(pdata);
hookmalloc(request);
}

(1)若涉及到分配新内存,则使用内联汇编技术获取当前程序地址,然后将其作为参数初值,循环调用 StackWalk64 接口获得完整的调用堆栈信息 CallStack(调用堆栈中各指令的地址信息),详见 getstacktrace() 函数,vld.cpp 第 530~592 行,接着与 request 值关联一起插入到 m_mallocmap 中。如下所示,其中的 pStackWalk64 是一个函数指针,指向 dbghelp.dll 库中的 StackWalk64 函数。

void VisualLeakDetector::getstacktrace (CallStack *callstack)
{
DWORD architecture;
CONTEXT context;
unsigned int count = 0;
STACKFRAME64 frame;
DWORD_PTR framepointer;
DWORD_PTR programcounter; // Get the required values for initialization of the STACKFRAME64 structure
// to be passed to StackWalk64(). Required fields are AddrPC and AddrFrame.
#if defined(_M_IX86) || defined(_M_X64)
architecture = X86X64ARCHITECTURE;
programcounter = getprogramcounterx86x64();
__asm mov [framepointer], BPREG // Get the frame pointer (aka base pointer)
#else
// If you want to retarget Visual Leak Detector to another processor
// architecture then you'll need to provide architecture-specific code to
// retrieve the current frame pointer and program counter in order to initialize
// the STACKFRAME64 structure below.
#error "Visual Leak Detector is not supported on this architecture."
#endif // defined(_M_IX86) || defined(_M_X64) // Initialize the STACKFRAME64 structure.
memset(&frame, 0x0, sizeof(frame));
frame.AddrPC.Offset = programcounter;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = framepointer;
frame.AddrFrame.Mode = AddrModeFlat; // Walk the stack.
while (count < _VLD_maxtraceframes) {
count++;
if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context,
NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {
// Couldn't trace back through any more frames.
break;
}
if (frame.AddrFrame.Offset == 0) {
// End of stack.
break;
} // Push this frame's program counter onto the provided CallStack.
callstack->push_back((DWORD_PTR)frame.AddrPC.Offset);
}
}

通过内联汇编获取当前程序地址的代码详见 getprogramcounterx86x64() 函数,vld.cpp 第 501~528 行,如下,通过 return 这个函数的返回地址得到。

// getprogramcounterx86x64 - Helper function that retrieves the program counter
// (aka the EIP (x86) or RIP (x64) register) for getstacktrace() on Intel x86
// or x64 architectures (x64 supports both AMD64 and Intel EM64T). There is no
// way for software to directly read the EIP/RIP register. But it's value can
// be obtained by calling into a function (in our case, this function) and
// then retrieving the return address, which will be the program counter from
// where the function was called.
//
// Note: Inlining of this function must be disabled. The whole purpose of this
// function's existence depends upon it being a *called* function.
//
// Return Value:
//
// Returns the caller's program address.
//
#if defined(_M_IX86) || defined(_M_X64)
#pragma auto_inline(off)
DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 ()
{
DWORD_PTR programcounter; __asm mov AXREG, [BPREG + SIZEOFPTR] // Get the return address out of the current stack frame
__asm mov [programcounter], AXREG // Put the return address into the variable we'll return return programcounter;
}
#pragma auto_inline(on)
#endif // defined(_M_IX86) || defined(_M_X64)

(2)若涉及到释放旧内存,则从 m_mallocmap 中去除这个内存块对应的 request 值及 CallStack 信息,详见 hookfree() 函数。

3.3 遍历双向链表生成泄漏检测报告

程序结束时,全局对象 visualleakdetector 的析构函数最后被调用(因为构造顺序与析构顺序相反)。在它的析构函数中(详见 vld.cpp 第 97~173 行),主要做了以下几件事:

(1)注销自定义 AllocHook 函数。

// Unregister the hook function.
pprevhook = _CrtSetAllocHook(m_poldhook);
if (pprevhook != allochook) {
// WTF? Somebody replaced our hook before we were done. Put theirs
// back, but notify the human about the situation.
_CrtSetAllocHook(pprevhook);
report("WARNING: Visual Leak Detector: The CRT allocation hook function was unhooked prematurely!\n"
" There's a good possibility that any potential leaks have gone undetected!\n");
}

(2)生成泄漏检测报告。详见 reportleaks() 函数,vld.cpp 第 802~962 行。报告生成思路如下:

  • Debug 模式下,每次分配内存时,系统都会给分配的数据块加上一个内存管理头 _CrtMemBlockHeader,如下所示,这个结构体有 pBlockHeaderNextpBlockHeaderPrev 两个成员变量,通过它们可以访问到其他已分配的内存块,全部的内存管理头组合在一起形成了一个双向链表结构,而新加入的内存管理头会被放置在该链表的头部。当释放内存时,对应的节点会在链表中被剔除。

    typedef struct _CrtMemBlockHeader {
    struct _CrtMemBlockHeader* pBlockHeaderNext;
    struct _CrtMemBlockHeader* pBlockHeaderPrev;
    char* szFileName;
    int nLine;
    #ifdef _WIN64
    /* These items are reversed on Win64 to eliminate gaps in the struct
    * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
    * maintained in the debug heap.
    */
    int nBlockUse;
    size_t nDataSize;
    #else /* _WIN64 */
    size_t nDataSize;
    int nBlockUse;
    #endif /* _WIN64 */
    long lRequest;
    unsigned char gap[nNoMansLandSize];
    /* followed by:
    * unsigned char data[nDataSize];
    * unsigned char anotherGap[nNoMansLandSize];
    */
    } _CrtMemBlockHeader;

    因此只需要临时 new 一块内存,就可以根据这个临时内存块的地址推导出链表头指针,如下代码中的 pheader。然后遍历这个链表,可以得到程序快结束时,仍未释放的内存信息。

    pheap = new char;
    pheader = pHdr(pheap)->pBlockHeaderNext;
    delete pheap;
  • 遍历过程中,依据每个节点的 nBlockUse 值,可以分辨出当前内存块的来由:用户分配、CRT 分配、还是 VLD 分配,据此可做一个筛选,例如只考虑来自用户分配的内存。

    if (_BLOCK_TYPE(pheader->nBlockUse) == _CRT_BLOCK) {
    // Skip internally allocated blocks.
    pheader = pheader->pBlockHeaderNext;
    continue;
    }
  • 遍历过程中,在 m_mallocmap 中查找筛选后节点的 lRequest 值,若存在,则表明有内存泄漏发生,由此可以获得发生泄漏的调用堆栈信息 CallStack,这是一系列指令地址。接下来,将这些指令地址作为输入参数,循环调用 SymGetLineFromAddr64 获得源文件名和行数,调用 SymFromAddr 获得函数名。将获取的信息传递给 report() 函数。

    callstack = m_mallocmap->find(pheader->lRequest);
    if (callstack) {
    // Found a block which is still in the allocated list, and which we
    // have an entry for in the allocated block map. We've identified a
    // memory leak.
    if (leaksfound == 0) {
    report("WARNING: Visual Leak Detector detected memory leaks!\n");
    }
    leaksfound++;
    report("---------- Block %ld at "ADDRESSFORMAT": %u bytes ----------\n", pheader->lRequest, pbData(pheader), pheader->nDataSize);
    if (_VLD_configflags & VLD_CONFIG_AGGREGATE_DUPLICATES) {
    // Aggregate all other leaks which are duplicates of this one
    // under this same heading, to cut down on clutter.
    duplicates = eraseduplicates(pheader->pBlockHeaderNext, pheader->nDataSize, callstack);
    if (duplicates) {
    report("A total of %lu leaks match this size and call stack. Showing only the first one.\n", duplicates + 1);
    leaksfound += duplicates;
    }
    }
    report(" Call Stack:\n"); // Iterate through each frame in the call stack.
    for (frame = 0; frame < callstack->size(); frame++) {
    // Try to get the source file and line number associated with
    // this program counter address.
    if ((foundline = pSymGetLineFromAddr64(m_process, (*callstack)[frame], &displacement, &sourceinfo)) == TRUE) {
    // Unless the "show useless frames" option has been enabled,
    // don't show frames that are internal to the heap or Visual
    // Leak Detector. There is virtually no situation where they
    // would be useful for finding the source of the leak.
    if (!(_VLD_configflags & VLD_CONFIG_SHOW_USELESS_FRAMES)) {
    if (strstr(sourceinfo.FileName, "afxmem.cpp") ||
    strstr(sourceinfo.FileName, "dbgheap.c") ||
    strstr(sourceinfo.FileName, "new.cpp") ||
    strstr(sourceinfo.FileName, "vld.cpp")) {
    continue;
    }
    }
    } // Try to get the name of the function containing this program
    // counter address.
    if (pSymFromAddr(m_process, (*callstack)[frame], &displacement64, pfunctioninfo)) {
    functionname = pfunctioninfo->Name;
    }
    else {
    functionname = "(Function name unavailable)";
    } // Display the current stack frame's information.
    if (foundline) {
    report(" %s (%d): %s\n", sourceinfo.FileName, sourceinfo.LineNumber, functionname);
    }
    else {
    report(" "ADDRESSFORMAT" (File and line number not available): ", (*callstack)[frame]);
    report("%s\n", functionname);
    }
    } // Dump the data in the user data section of the memory block.
    if (_VLD_maxdatadump != 0) {
    dumpuserdatablock(pheader);
    }
    report("\n");
    }
  • report() 函数中格式化后再使用 OutputDebugString 输出泄漏报告。这里用到了 C 语言中的变长参数,用法可参考 博客园-C++ 实现可变参数的三个方法

    // report - Sends a printf-style formatted message to the debugger for display.
    //
    // - format (IN): Specifies a printf-compliant format string containing the
    // message to be sent to the debugger.
    //
    // - ... (IN): Arguments to be formatted using the specified format string.
    //
    // Return Value:
    //
    // None.
    //
    void VisualLeakDetector::report (const char *format, ...)
    {
    va_list args;
    #define MAXREPORTMESSAGESIZE 513
    char message [MAXREPORTMESSAGESIZE]; va_start(args, format);
    _vsnprintf(message, MAXREPORTMESSAGESIZE, format, args);
    va_end(args);
    message[MAXREPORTMESSAGESIZE - 1] = '\0'; OutputDebugString(message);
    }

(3)卸载 dbghelp.dll 库。

// Unload the Debug Help Library.
FreeLibrary(m_dbghelp);

(4)泄漏自检。通过遍历系统用于内存管理的双向链表,判断 VLD 自身是否发生内存泄漏,同样是依据每个节点的 nBlockUse 值。

// Do a memory leak self-check.
pheap = new char;
pheader = pHdr(pheap)->pBlockHeaderNext;
delete pheap;
while (pheader) {
if (_BLOCK_SUBTYPE(pheader->nBlockUse) == VLDINTERNALBLOCK) {
// Doh! VLD still has an internally allocated block!
// This won't ever actually happen, right guys?... guys?
internalleaks++;
leakfile = pheader->szFileName;
leakline = pheader->nLine;
report("ERROR: Visual Leak Detector: Detected a memory leak internal to Visual Leak Detector!!\n");
report("---------- Block %ld at "ADDRESSFORMAT": %u bytes ----------\n", pheader->lRequest, pbData(pheader), pheader->nDataSize);
report("%s (%d): Full call stack not available.\n", leakfile, leakline);
dumpuserdatablock(pheader);
report("\n");
}
pheader = pheader->pBlockHeaderNext;
}
if (_VLD_configflags & VLD_CONFIG_SELF_TEST) {
if ((internalleaks == 1) && (strcmp(leakfile, m_selftestfile) == 0) && (leakline == m_selftestline)) {
report("Visual Leak Detector passed the memory leak self-test.\n");
}
else {
report("ERROR: Visual Leak Detector: Failed the memory leak self-test.\n");
}
}

(5)输出卸载成功的提示信息。这一输出发生在析构函数的结尾括号 } 前。

report("Visual Leak Detector is now exiting.\n");

4. 其他问题

4.1 如何区分分配内存的来由

_CrtMemBlockHeader 结构体有个 nBlockUse 成员变量,用来标识分配用途,这个值是可以人为设置的,VLD 正是利用这一点,重载了 VLD 内部使用的内存分配函数,使得库内部每次进行内存请求时,都会将这个 nBlockUse 设置为 VLD 分配标识,详见 vldutil.h 第 49~153 行。

(1)分配时,核心代码如下,第二个参数为设置的 nBlockUse 值:

void *pdata = _malloc_dbg(size, _CRT_BLOCK | (VLDINTERNALBLOCK << 16), file, line);

(2)使用 nBlockUse 来对分配用途做判断时,核心代码如下:

// 判断是否由 CRT 或 VLD 分配
if (_BLOCK_TYPE(pheader->nBlockUse) == _CRT_BLOCK) {
...
} // 判断是否由 VLD 分配
if (_BLOCK_SUBTYPE(pheader->nBlockUse) == VLDINTERNALBLOCK) {
...
}

(3)这里面涉及到的几个宏定义如下:

文件 crtdbg.h 中。

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block) (block >> 16 & 0xFFFF) // Memory block identification
#define _FREE_BLOCK 0
#define _NORMAL_BLOCK 1
#define _CRT_BLOCK 2
#define _IGNORE_BLOCK 3
#define _CLIENT_BLOCK 4
#define _MAX_BLOCKS 5

文件 vldutil.h 中。

#define VLDINTERNALBLOCK   0xbf42    // VLD internal memory block subtype

4.2 如何实现多线程检测

使用线程本地存储(Thread Local Storage),参考 MicroSoft-Using-Thread-Local-Storage。全局对象 visualleakdetector 有个成员变量 m_tlsindex,详见 vldint.h 第 146 行,如下:

DWORD m_tlsindex;     // Index for thread-local storage of VLD data

这个变量被用来接收 TlsAlloc() 返回的索引值,在 visualleakdetector 的构造函数中被初始化,详见 vld.cpp 第 69 行、77~79 行,如下:

m_tlsindex = TlsAlloc();

... 

if (m_tlsindex == TLS_OUT_OF_INDEXES) {
report("ERROR: Visual Leak Detector: Couldn't allocate thread local storage.\n");
}

初始化成功后,当前进程的任何线程都可以使用这个索引值来存储和访问对应线程本地的值,不同线程间互不影响,访问获得的结果也与其他线程无关,因此可用它来存储 VLD 在每个线程中的开关状态。在分配新内存时,会触发 hookmalloc() 函数,该函数会在分配行为所属的线程中执行,详见 vld.cpp 第 611~636 行:

void VisualLeakDetector::hookmalloc (long request)
{
CallStack *callstack; if (!enabled()) {
// Memory leak detection is disabled. Don't track allocations.
return;
} callstack = m_mallocmap->insert(request);
getstacktrace(callstack);
}

(1)判断当前线程是否开启了 VLD。在 enabled() 函数中,会调用 TlsGetValue() 访问所属线程本地的值,根据此值判断 VLD 内存检测功能是否处于开启状态。若是第一次访问(此时 TlsGetValue() 的返回值为 VLD_TLS_UNINITIALIZED),则根据用户配置,使用 TlsSetValue() 初始化对应线程本地的值。

// enabled - Determines if memory leak detection is enabled for the current
// thread.
//
// Return Value:
//
// Returns true if Visual Leak Detector is enabled for the current thread.
// Otherwise, returns false.
//
bool VisualLeakDetector::enabled ()
{
unsigned long status; status = (unsigned long)TlsGetValue(m_tlsindex);
if (status == VLD_TLS_UNINITIALIZED) {
// TLS is uninitialized for the current thread. Use the initial state.
if (_VLD_configflags & VLD_CONFIG_START_DISABLED) {
status = VLD_TLS_DISABLED;
}
else {
status = VLD_TLS_ENABLED;
}
// Initialize TLS for this thread.
TlsSetValue(m_tlsindex, (LPVOID)status);
} return (status & VLD_TLS_ENABLED) ? true : false;
}

(2)对当前线程设置 VLD 的开关状态。这是两个对外的接口函数,其定义如下,详见 vldapi.cpp 第 31~57 行,使用 TlsSetValue() 设置对应值即可:

void VLDEnable ()
{
if (visualleakdetector.enabled()) {
// Already enabled for the current thread.
return;
} // Enable memory leak detection for the current thread.
TlsSetValue(visualleakdetector.m_tlsindex, (LPVOID)VLD_TLS_ENABLED);
visualleakdetector.m_status &= ~VLD_STATUS_NEVER_ENABLED;
} void VLDDisable ()
{
if (!visualleakdetector.enabled()) {
// Already disabled for the current thread.
return;
} // Disable memory leak detection for the current thread.
TlsSetValue(visualleakdetector.m_tlsindex, (LPVOID)VLD_TLS_DISABLED);
}

【Visual Leak Detector】核心源码剖析(VLD 1.0)的更多相关文章

  1. 四、Kafka 核心源码剖析

    一.Kafka消费者源码介绍 1.分区消费模式源码介绍 分区消费模式直接由客户端(任何高级语言编写)使用Kafka提供的协议向服务器发送RPC请求获取数据,服务器接受到客户端的RPC请求后,将数据构造 ...

  2. Visual Leak Detector原理剖析

    认识VLD VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,我们只需要在被检测内存泄漏的工程代码里#include “vld.h”就可以开启内存 ...

  3. VS2017 编译 Visual Leak Detector + VLD 使用示例

    起因 一个Qt5+VS2017的工程,需要进行串口操作,在自动时发现一段时间软件崩溃了,没有保存log,在 debug 的时候发现每运行一次应用占据的内存就多一点,后来意识到是内存泄漏了.这个真是头疼 ...

  4. vld(Visual Leak Detector) 内存泄露检测工具

    初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复 杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题.内存 ...

  5. 使用Visual Leak Detector for Visual C++ 捕捉内存泄露

    什么是内存泄漏? 内存泄漏(memory leak),指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段 ...

  6. 使用Visual Leak Detector检测内存泄漏[转]

      1.初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题 ...

  7. Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector

    那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简 ...

  8. Visual Leak Detector 2.2.3 Visual C++内存检测工具

      Visual Leak Detector是一款免费的.健全的.开源的Visual C++内存泄露检测系统.相比Visual C++自带的内存检测机制,Visual Leak Detector可以显 ...

  9. Cocos性能优化工具的开发介绍Visual Studio内存泄漏检测工具——Visual Leak Detector

    然后,Windows下有什么好的内存泄漏检測工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检測功能.我们能够使用第三方工具Visual Leak Detector(下面简 ...

  10. VisualStudio 怎么使用Visual Leak Detector

    VisualStudio 怎么使用Visual Leak Detector 那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测 ...

随机推荐

  1. JSON数据转对象遍历

    String json = "[{\"n\":\"北京\",\"i\":11,\"p\":0,\"y ...

  2. 10.7 2020 实验 5:OpenFlow 协议分析和 OpenDaylight 安装

    一.实验目的 回顾 JDK 安装配置,了解 OpenDaylight 控制的安装,以及 Mininet 如何连接:通过抓包获取 OpenFlow 协议,验证 OpenFlow 协议和版本,了解协议内容 ...

  3. GO语言http请求方法,可以携带请求头Header与cookie

    1.目录 2.main.go package main import "fmt" import "demo/common/http" func main() { ...

  4. 西电oj135题 拼数字并排序

    类别综合 时间限制 1S 内存限制 1000Kb 问题描述 对于输入的字符串(只包含字母和数字),将其中的连续数字拼接成整数,然后将这些整数按从大到小顺序输出.例如字符串"abc123d5e ...

  5. Unity ContentSizeFitter组件

    Content Size Fitter组件,它可以动态改变物体的宽高,但它有一个非常需要注意的点就是,它不是即时刷新,是帧末刷新,这个特性如果没注意会出现一个问题 就是你拿到加了这个组件的宽高本不是你 ...

  6. mysql-单行处理函数

    1 单行处理函数 lower() 对于输出转换成小写 upper()对于输出转换成大写 substr()取子字符串 下标从1开始 length() 去长度 concat()将字符串进行拼接 例:将首字 ...

  7. 为什么 C# 可能是最好的第一编程语言

    纵观神州大地,漫游中华互联网,我看到很多人关注为什么你应该开始学习JavaScript做前端,而对blazor这样的面向未来的框架有种莫名的瞧不起,或者为什么你应该学习Python作为你的第一门编程语 ...

  8. vue2升级vue3:vue-i18n国际化异步按需加载

    vue2异步加载之前说过,vue3还是之前的方法,只是把 i18n.setLocaleMessage改为i18n.global.setLocaleMessage 但是本文还是详细说一遍: 为什么需要异 ...

  9. ElasticSearch的常用API

    ElasticSearch的常用API 1.在服务器上怎么查ES的信息 # 通过使用_cat可以查看支持的命令 ### curl localhost:9200/_cat eg: /_cat/alloc ...

  10. Solon2 接口开发: 分布式 Api Gateway 开发预览

    建议使用专业的分布式网关产品,比如: nginx apisix [推荐] k8s ingress controller 等... 对 Solon 来讲,只有 Gateway:它调用本地接口时,则为本地 ...