网站推广--html关键词代码解说,家居装修设计,事业单位门户网站建设的建议,替朋友做网站From: http://www.xdowns.com/article/170/Article_3060.html 初识Visual Leak Detector 灵活自由是C/C语言的一大特色#xff0c;而这也为C/C程序员出了一个难题。当程序越来越复杂时#xff0c;内存的管理也会变得越加复杂#xff0c;稍有不慎就会出现内存问题。内…From: http://www.xdowns.com/article/170/Article_3060.html 初识Visual Leak Detector 灵活自由是C/C语言的一大特色而这也为C/C程序员出了一个难题。当程序越来越复杂时内存的管理也会变得越加复杂稍有不慎就会出现内存问题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严重在短时间内对程序不会有太大的影响这也使得内存泄漏问题有很强的隐蔽性不容易被发现。然而不管内存泄漏多么轻微当程序长时间运行时其破坏力是惊人的从性能下降到内存耗尽甚至会影响到其他程序的正常运行。另外内存问题的一个共同特点是内存问题本身并不会有很明显的现象当有异常现象出现时已时过境迁其现场已非出现问题时的现场了这给调试内存问题带来了很大的难度。 Visual Leak Detector是一款用于Visual C的免费的内存泄露检测工具。可以在Visual Leak Detector 1.9 - VC内存泄露检查工具 下载到。相比较其它的内存泄露检测工具它在检测到内存泄漏的同时还具有如下特点 1、 可以得到内存泄漏点的调用堆栈如果可以的话还可以得到其所在文件及行号 2、 可以得到泄露内存的完整数据 3、 可以设置内存泄露报告的级别 4、 它是一个已经打包的lib使用时无须编译它的源代码。而对于使用者自己的代码也只需要做很小的改动 5、 他的源代码使用GNU许可发布并有详尽的文档及注释。对于想深入了解堆内存管理的读者是一个不错的选择。 可见从使用角度来讲Visual Leak Detector简单易用对于使用者自己的代码唯一的修改是#include Visual Leak Detector的头文件后正常运行自己的程序就可以发现内存问题。从研究的角度来讲如果深入Visual Leak Detector源代码可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存操作的常用技巧等。 本文首先将介绍Visual Leak Detector的使用方法与步骤然后再和读者一起初步的研究Visual Leak Detector的源代码去了解Visual Leak Detector的工作原理。 使用Visual Leak Detector(1.0) 下面让我们来介绍如何使用这个小巧的工具。 首先从网站上下载zip包解压之后得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等文件。将.h文件拷贝到Visual C的默认include目录下将.lib文件拷贝到Visual C的默认lib目录下便安装完成了。因为版本问题如果使用windows 2000或者以前的版本需要将dbghelp.dll拷贝到你的程序的运行目录下或其他可以引用到的目录。 接下来需要将其加入到自己的代码中。方法很简单只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件包含了stdafx.h则将包含vld.h的语句放在stdafx.h的包含语句之后否则放在最前面。如下是一个示例程序 #include vld.h void main() { … } 接下来让我们来演示如何使用Visual Leak Detector检测内存泄漏。下面是一个简单的程序用new分配了一个int大小的堆内存并没有释放。其申请的内存地址用printf输出到屏幕上。 #include vld.h #include stdlib.h #include stdio.h void f() { int *p new int(0x12345678); printf(p%08x, , p); } void main() { f(); } 编译运行后在标准输出窗口得到 p003a89c0 在Visual C的Output窗口得到 WARNING: Visual Leak Detector detected memory leaks! ---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57号块0x003A89C0地址泄漏了4个字节 Call Stack: --下面是调用堆栈 d:\test\testvldconsole\testvldconsole\main.cpp (7): f --表示在main.cpp第7行的f()函数 d:\test\testvldconsole\testvldconsole\main.cpp (14): main –双击以引导至对应代码处 f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (586): __tmainCRTStartup f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (403): mainCRTStartup 0x7C816D4F (File and line number not available): RegisterWaitForInputIdle Data: --这是泄漏内存的内容0x12345678 78 56 34 12 xV4..... ........ Visual Leak Detector detected 1 memory leak. 第二行表示57号块有4字节的内存泄漏地址为0x003A89C0根据程序控制台的输出可以知道该地址为指针p。程序的第7行f()函数里在该地址处分配了4字节的堆内存空间并赋值为0x12345678这样在报告中我们看到了这4字节同样的内容。 可以看出对于每一个内存泄漏这个报告列出了它的泄漏点、长度、分配该内存时的调用堆栈、和泄露内存的内容分别以16进制和文本格式列出。双击该堆栈报告的某一行会自动在代码编辑器中跳到其所指文件的对应行。这些信息对于我们查找内存泄露将有很大的帮助。 这是一个很方便易用的工具安装后每次使用时仅仅需要将它头文件包含进来重新build就可以。而且该工具仅在build Debug版的时候会连接到你的程序中如果build Release版该工具不会对你的程序产生任何性能等方面影响。所以尽可以将其头文件一直包含在你的源代码中。 Visual Leak Detector工作原理 下面让我们来看一下该工具的工作原理。 在这之前我们先来看一下Visual C内置的内存泄漏检测工具是如何工作的。Visual C内置的工具CRT Debug Heap工作原来很简单。在使用Debug版的malloc分配内存时malloc会在内存块的头中记录分配该内存的文件名及行号。当程序退出时CRT会在main()函数返回之后做一些清理工作这个时候来检查调试堆内存如果仍然有内存没有被释放则一定是存在内存泄漏。从这些没有被释放的内存块的头中就可以获得文件名及行号。 这种静态的方法可以检测出内存泄漏及其泄漏点的文件名和行号但是并不知道泄漏究竟是如何发生的并不知道该内存分配语句是如何被执行到的。要想了解这些就必须要对程序的内存分配过程进行动态跟踪。Visual Leak Detector就是这样做的。它在每次内存分配时将其上下文记录下来当程序退出时对于检测到的内存泄漏查找其记录下来的上下文信息并将其转换成报告输出。 初始化 Visual Leak Detector要记录每一次的内存分配而它是如何监视内存分配的呢Windows提供了分配钩子(allocation hooks)来监视调试堆内存的分配。它是一个用户定义的回调函数在每次从调试堆分配内存之前被调用。在初始化时Visual Leak Detector使用_CrtSetAllocHook注册这个钩子函数这样就可以监视从此之后所有的堆内存分配了。 如何保证在Visual Leak Detector初始化之前没有堆内存分配呢全局变量是在程序启动时就初始化的如果将Visual Leak Detector作为一个全局变量就可以随程序一起启动。但是C/C并没有约定全局变量之间的初始化顺序如果其它全局变量的构造函数中有堆内存分配则可能无法检测到。Visual Leak Detector使用了C/C提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始化的概率。根据#pragma init_seg的定义全局变量的初始化分三个阶段首先是compiler段一般c语言的运行时库在这个时候初始化然后是lib段一般用于第三方的类库的初始化等最后是user段大部分的初始化都在这个阶段进行。Visual Leak Detector将其初始化设置在compiler段从而使得它在绝大多数全局变量和几乎所有的用户定义的全局变量之前初始化。 记录内存分配 一个分配钩子函数需要具有如下的形式 int YourAllocHook( int allocType, void *userData, size_t size, int blockType, long requestNumber, const unsigned char *filename, int lineNumber); 就像前面说的它在Visual Leak Detector初始化时被注册每次从调试堆分配内存之前被调用。这个函数需要处理的事情是记录下此时的调用堆栈和此次堆内存分配的唯一标识——requestNumber。 得到当前的堆栈的二进制表示并不是一件很复杂的事情但是因为不同体系结构、不同编译器、不同的函数调用约定所产生的堆栈内容略有不同要解释堆栈并得到整个函数调用过程略显复杂。不过windows提供一个StackWalk64函数可以获得堆栈的内容。StackWalk64的声明如下 BOOL StackWalk64( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); STACKFRAME64结构表示了堆栈中的一个frame。给出初始的STACKFRAME64反复调用该函数便可以得到内存分配点的调用堆栈了。 // Walk the stack. while (count _VLD_maxtraceframes) { count; if (!pStackWalk64(architecture, m_process, m_thread, frame, context, NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) { // Couldnt trace back through any more frames. break; } if (frame.AddrFrame.Offset 0) { // End of stack. break; } // Push this frames program counter onto the provided CallStack. callstack-push_back((DWORD_PTR)frame.AddrPC.Offset); } 那么如何得到初始的STACKFRAME64结构呢在STACKFRAME64结构中其他的信息都比较容易获得而当前的程序计数器(EIP)在x86体系结构中无法通过软件的方法直接读取。Visual Leak Detector使用了一种方法来获得当前的程序计数器。首先它调用一个函数则这个函数的返回地址就是当前的程序计数器而函数的返回地址可以很容易的从堆栈中拿到。下面是Visual Leak Detector获得当前程序计数器的程序 #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 well return return programcounter; } #pragma auto_inline(on) #endif // defined(_M_IX86) || defined(_M_X64) 得到了调用堆栈自然要记录下来。Visual Leak Detector使用一个类似map的数据结构来记录该信息。这样可以方便的从requestNumber查找到其调用堆栈。分配钩子函数的allocType参数表示此次堆内存分配的类型包括_HOOK_ALLOC, _HOOK_REALLOC, 和 _HOOK_FREE下面代码是Visual Leak Detector对各种情况的处理。 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; } 这里hookmalloc()函数得到当前堆栈并将当前堆栈与requestNumber加入到类似map的数据结构中。hookfree()函数从类似map的数据结构中删除该信息。hookrealloc()函数依次调用了hookfree()和hookmalloc()。 检测内存泄露 前面提到了Visual C内置的内存泄漏检测工具的工作原理。与该原理相同因为全局变量以构造的相反顺序析构在Visual Leak Detector析构时几乎所有的其他变量都已经析构此时如果仍然有未释放之堆内存则必为内存泄漏。 分配的堆内存是通过一个链表来组织的检查内存泄漏则是检查此链表。但是windows没有提供方法来访问这个链表。Visual Leak Detector使用了一个小技巧来得到它。首先在堆上申请一块临时内存则该内存的地址可以转换成指向一个_CrtMemBlockHeader结构在此结构中就可以获得这个链表。代码如下 char *pheap new char; _CrtMemBlockHeader *pheader pHdr(pheap)-pBlockHeaderNext; delete pheap; 其中pheader则为链表首指针。 报告生成 前面讲了Visual Leak Detector如何检测、记录内存泄漏及其其调用堆栈。但是如果要这个信息对程序员有用的话必须转换成可读的形式。Visual Leak Detector使用SymGetLineFromAddr64()及SymFromAddr()生成可读的报告。 // 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 (pSymGetLineFromAddr64(m_process, (*callstack)[frame], displacement, sourceinfo)) { ... } // 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); } ... } 概括讲来Visual Leak Detector的工作分为3步首先在初始化注册一个钩子函数然后在内存分配时该钩子函数被调用以记录下当时的现场最后检查堆内存分配链表以确定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。有兴趣的读者可以阅读Visual Leak Detector的源代码。 总结 在使用上Visual Leak Detector简单方便结果报告一目了然。在原理上Visual Leak Detector针对内存泄漏问题的特点可谓对症下药——内存泄漏不是不容易发现吗那就每次内存分配是都给记录下来程序退出时算总账内存泄漏现象出现时不是已时过境迁并非当时泄漏点的现场了吗那就把现场也记录下来清清楚楚的告诉使用者那块泄漏的内存就是在如何一个调用过程中泄漏掉的。 Visual Leak Detector是一个简单易用内存泄漏检测工具。现在最新的版本是1.9a采用了新的检测机制并在功能上有了很多改进。读者不妨体验一下。