|
2013年10月22日
编译时遇到这么个问题 百思不得其解 网上有说是图标出问题 可我这个肯定没问题
最后才发现我太懒了 本来是BMP定义的资源
RC_IMAGELIST_WENJIAN BMP DISCARDABLE "img\\wenjian.bmp"
我直接复制后该了下名字为
RC_IMAGELIST_WENJIAN_ICO BMP DISCARDABLE "img\\wenjian.ico"
忘了把中间的类型换成ICON所以出错 杯具!
RC_IMAGELIST_WENJIAN_ICO ICON DISCARDABLE "img\\wenjian.ico"
这样就对了!
2013年10月11日
初识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(); } 编译运行后,在标准输出窗口得到: p=003a89c0 在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)) { // 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); } 那么,如何得到初始的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 we'll 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,采用了新的检测机制,并在功能上有了很多改进。读者不妨体验一下。
1、在调试状态下让win程在输出窗口中显示调试信息,可以用_RPTn 宏n为显示参数比如_RPT0(_CRT_WARN,"text"); _RPT1(_CRT_WARN,"%d", iTest); _RPT2(_CRT_WARN,"%d,%f", iTest, fTest);....依次类推,详细见msdn 2、侦测内存泄露用_CrtSetDbgFlag函数
_CrtDumpMemoryLeaks()在程序退出点之前调用会在输出中产生内存泄露信息,但是如果程序有多个退出点则无需 在所有退出点都调用该函数,取而代之的是简单的在main()一开始就首先调用
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);这样程序会在退出前自动调用_CrtDumpMemoryLeaks()
产生的信息类似:
Detected memory leaks! Dumping objects -> C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18} normal block at 0x00780E80, 64 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete. 要在Dumpint objects -> 后面产生文件名的话来确定 泄露文件与代码行号的话(见上面红字部分)则需要在 #include <crtdbg.h>之前加上宏#define _CRTDBG_MAP_ALLOC
#define _CRTDBG_MAP_ALLOC #include <stdlib.h> //msdn里面的例子加了该头文件 #include <crtdbg.h> 查找泄露技巧 使用_CrtSetBreakAlloc(long lBreakAlloc );函数参数为内存分配的次数,其值为调试信息的{}中的值 上面的例子为{18} 于是让程序自动在泄露处进入断点可以在_CrtSetDbgFlag后面添加函数_CrtSetBreakAlloc(18);然后调试程序时,程序 自动断在第18次分配内存的时的crt代码处,然后只要通过查看调用堆栈就可以轻松看到之前的泄露的代码了
C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:“最大的长处也可能成为最大的弱点”,那么 C/C++ 应用程序正好印证了这句话。在 C/C++ 应用程序开发过程中,动态分配的内存处理不当是最常见的问题。其中,最难捉摸也最难检测的错误之一就是内存泄漏,即未能正确释放以前分配的内存的错误。偶 尔发生的少量内存泄漏可能不会引起我们的注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种 各样的征兆:从性能不良(并且逐渐降低)到内存完全耗尽。更糟的是,泄漏的程序可能会用掉太多内存,导致另外一个程序垮掉,而使用户无从查找问题的真正根 源。此外,即使无害的内存泄漏也可能殃及池鱼。
幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏?
一、如何启用内存泄漏检测机制
VC++ IDE 的默认状态是没有启用内存泄漏检测机制的,也就是说即使某段代码有内存泄漏,调试会话的 Output 窗口的 Debug 页不会输出有关内存泄漏信息。你必须设定两个最基本的机关来启用内存泄漏检测机制。
一是使用调试堆函数: #define _CRTDBG_MAP_ALLOC #include<stdlib.h>
#include<crtdbg.h>
注意:#include 语句的顺序。如果更改此顺序,所使用的函数可能无法正确工作。
通过包含 crtdbg.h 头文件,可以将 malloc 和 free 函数映射到其“调试”版本 _malloc_dbg 和 _free_dbg,这些函数会跟踪内存分配和释放。此映射只在调试(Debug)版本(也就是要定义 _DEBUG)中有效。发行版本(Release)使用普通的 malloc 和 free 函数。#define 语句将 CRT 堆函数的基础版本映射到对应的“调试”版本。该语句不是必须的,但如果没有该语句,那么有关内存泄漏的信息会不全。
二是在需要检测内存泄漏的地方添加下面这条语句来输出内存泄漏信息:
_CrtDumpMemoryLeaks();
当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在 Output 窗口的 Debug 页中显示内存泄漏信息。比如: Detected memory leaks! Dumping objects ->
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: <AB> 41 42
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {44} normal block at 0x00441BD0, 33 bytes long.
Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) : {43} normal block at 0x00441C20, 40 bytes long.
Data: < C > 08 02 43 00 16 00 00 00 00 00 00 00 00 00 00 00
Object dump complete.
如果不使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏的输出是这样的:
Detected memory leaks!
Dumping objects ->
{45} normal block at 0x00441BA0, 2 bytes long. Data: <AB> 41 42
{44} normal block at 0x00441BD0, 33 bytes long. Data: < C > 00 43 00 CD CD CD CD CD CD CD CD CD CD CD CD CD
{43} normal block at 0x00441C20, 40 bytes long. Data: < C > C0 01 43 00 16 00 00 00 00 00 00 00 00 00 00 00
Object dump complete.
根据这段输出信息,你无法知道在哪个源程序文件里发生了内存泄漏。下面我们来研究一下输出信息的格式。第一行和第二行没有什么可说的,从第三行开始:
xx}:花括弧内的数字是内存分配序号,本文例子中是 {45},{44},{43}; block:内存块的类型,常用的有三种:normal(普通)、client(客户端)或 CRT(运行时);本文例子中是:normal block; 用十六进制格式表示的内存位置,如:at 0x00441BA0 等; 以字节为单位表示的内存块的大小,如:32 bytes long; 前 16 字节的内容(也是用十六进制格式表示),如:Data: 41 42 等;
仔细观察不难发现,如果定义了 _CRTDBG_MAP_ALLOC ,那么在内存分配序号前面还会显示在其中分配泄漏内存的文件名,以及文件名后括号中的数字表示发生泄漏的代码行号,比如:
C:\Temp\memleak\memleak.cpp(15)
双击 Output 窗口中此文件名所在的输出行,便可跳到源程序文件分配该内存的代码行(也可以选中该行,然后按 F4,效果一样) ,这样一来我们就很容易定位内存泄漏是在哪里发生的了,因此,_CRTDBG_MAP_ALLOC 的作用显而易见。
使用 _CrtSetDbgFlag 如果程序只有一个出口,那么调用 _CrtDumpMemoryLeaks 的位置是很容易选择的。但是,如果程序可能会在多个地方退出该怎么办呢?在每一个可能的出口处调用 _CrtDumpMemoryLeaks 肯定是不可取的,那么这时可以在程序开始处包含下面的调用:_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );这条语句无论程序在什么地方退出都会自动调用 _CrtDumpMemoryLeaks。注意:这里必须同时设置两个位域标志:_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。
设置 CRT 报告模式 默认情况下,_CrtDumpMemoryLeaks 将内存泄漏信息 dump 到 Output 窗口的 Debug 页, 如果你想将这个输出定向到别的地方,可以使用 _CrtSetReportMode 进行重置。如果你使用某个库,它可能将输出定向到另一位置。此时,只要使用以下语句将输出位置设回 Output 窗口即可:
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );
有关使用 _CrtSetReportMode 的详细信息,请参考 MSDN 库关于 _CrtSetReportMode 的描述。
二、解释内存块类型
前面已经说过,内存泄漏报告中把每一块泄漏的内存分为 normal(普通块)、client(客户端块)和 CRT 块。事实上,需要留心和注意的也就是 normal 和 client,即普通块和客户端块。 1.normal block(普通块):这是由你的程序分配的内存。 2.client block(客户块):这是一种特殊类型的内存块,专门用于 MFC 程序中需要析构函数的对象。MFC new 操作符视具体情况既可以为所创建的对象建立普通块,也可以为之建立客户块。 3.CRT block(CRT 块):是由 C RunTime Library 供自己使用而分配的内存块。由 CRT 库自己来管理这些内存的分配与释放,我们一般不会在内存泄漏报告中发现 CRT 内存泄漏,除非程序发生了严重的错误(例如 CRT 库崩溃)。
除了上述的类型外,还有下面这两种类型的内存块,它们不会出现在内存泄漏报告中: 1.free block(空闲块):已经被释放(free)的内存块。 2.Ignore block(忽略块):这是程序员显式声明过不要在内存泄漏报告中出现的内存块。
三、如何在内存分配序号处设置断点
在内存泄漏报告中,的文件名和行号可告诉分配泄漏的内存的代码位置,但仅仅依赖这些信息来了解完整的泄漏原因是不够的。因为一个程序在运行时,一段分配 内存的代码可能会被调用很多次,只要有一次调用后没有释放内存就会导致内存泄漏。为了确定是哪些内存没有被释放,不仅要知道泄漏的内存是在哪里分配的,还 要知道泄漏产生的条件。这时内存分配序号就显得特别有用——这个序号就是文件名和行号之后的花括弧里的那个数字。
例如,在本文例子代码的输出信息中,“45”是内存分配序号,意思是泄漏的内存是你程序中分配的第四十五个内存块:
Detected memory leaks!
Dumping objects ->
C:\Temp\memleak\memleak.cpp(15) : {45} normal block at 0x00441BA0, 2 bytes long.
Data: <AB> 41 42
......
Object dump complete.
CRT 库对程序运行期间分配的所有内存块进行计数,包括由 CRT 库自己分配的内存和其它库(如 MFC)分配的内存。因此,分配序号为 N 的对象即为程序中分配的第 N 个对象,但不一定是代码分配的第 N 个对象。(大多数情况下并非如此。)这样的话,你便可以利用分配序号在分配内存的位置设置一个断点。方法是在程序起始附近设置一个位置断点。当程序在该点 中断时,可以从 QuickWatch(快速监视)对话框或 Watch(监视)窗口设置一个内存分配断点:
例如,在 Watch 窗口中,在 Name 栏键入下面的表达式:
_crtBreakAlloc
如果要使用 CRT 库的多线程 DLL 版本(/MD 选项),那么必须包含上下文操作符,像这样:
{,,msvcrtd.dll}_crtBreakAlloc
现在按下回车键,调试器将计算该值并把结果放入 Value 栏。如果没有在内存分配点设置任何断点,该值将为 –1。
用你想要在其位置中断的内存分配的分配序号替换 Value 栏中的值。例如输入 45。这样就会在分配序号为 45 的地方中断。
在所感兴趣的内存分配处设置断点后,可以继续调试。这时,运行程序时一定要小心,要保证内存块分配的顺序不会改变。当程序在指定的内存分配处中断时,可 以查看 Call Stack(调用堆栈)窗口和其它调试器信息以确定分配内存时的情况。如果必要,可以从该点继续执行程序,以查看对象发生了什么情况,或许可以确定未正确 释放对象的原因。
尽管通常在调试器中设置内存分配断点更方便,但如果愿意,也可在代码中设置这些断点。为了在代码中设置一个内存分配断点,可以增加这样一行(对于第四十五个内存分配):
_crtBreakAlloc = 45;
你还可以使用有相同效果的 _CrtSetBreakAlloc 函数:
_CrtSetBreakAlloc(45);
四、如何比较内存状态
定位内存泄漏的另一个方法就是在关键点获取应用程序内存状态的快照。CRT 库提供了一个结构类型 _CrtMemState。
你可以用它来存储内存状态的快照:
_CrtMemState s1, s2, s3;
若要获取给定点的内存状态快照,可以向 _CrtMemCheckpoint 函数传递一个 _CrtMemState 结构。该函数用当前内存状态的快照填充此结构:
_CrtMemCheckpoint( &s1 );
通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意地方 dump 该结构的内容:
_CrtMemDumpStatistics( &s1 );
该函数输出如下格式的 dump 内存分配信息:
0 bytes in 0 Free Blocks. 75 bytes in 3 Normal Blocks. 5037 bytes in 41 CRT Blocks. 0 bytes in 0 Ignore Blocks. 0 bytes in 0 Client Blocks. Largest number used: 5308 bytes. Total allocations: 7559 bytes.
若要确定某段代码中是否发生了内存泄漏,可以通过获取该段代码之前和之后的内存状态快照,然后使用 _CrtMemDifference 比较这两个状态:
_CrtMemCheckpoint( &s1 );// 获取第一个内存状态快照
// 在这里进行内存分配
_CrtMemCheckpoint( &s2 );// 获取第二个内存状态快照
// 比较两个内存快照的差异 if ( _CrtMemDifference( &s3, &s1, &s2) ) _CrtMemDumpStatistics( &s3 );// dump 差异结果
顾名思义,_CrtMemDifference 比较两个内存状态(前两个参数),生成这两个状态之间差异的结果(第三个参数)。在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用 _CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来分割程序和定位泄漏。
五、结论
尽管 VC ++ 具有一套专门调试 MFC 应用程序的机制,但本文上述讨论的内存分配很简单,没有涉及到 MFC 对象,所以这些内容同样也适用于 MFC 程序。在 MSDN 库中可以找到很多有关 VC++ 调试方面的资料,如果你能善用 MSDN 库,相信用不了多少时间你就有可能成为调试高手。
2013年3月20日
今天遇到一个问题,调用中间件的数据,因为本地维护的list势行了merg操作清空了中间件的list,导至其它页面访问中间件的这份数据时不成功。 我认为这是中间件开发者的一个很严重疏忽。中间件向外提供数据时,只读的特性是必要的,如果所有访问者都能修改,极易引起混乱,而且在复杂应用中查错将是一件十分可怕的事。 多用户访问时,数据读写锁机制也是必要的。
摘要: 各个容器有很多的相似性。先学好一个,其它的就好办了。先从基础开始。 先看看他们的分类吧 标准STL序列容器:vector、string、deque和list。 标准STL关联容器:set、multiset、map和multimap。 非标准序列容器slist和rope。slist是一个单向链表,rope本质上是一个重型字符串。 非标准关联容器hash_set、hash_multiset、has... 阅读全文
2013年1月9日
有两种应用场景: 1、在除了标题栏以外,点击窗口的其它部分可以拖动窗口。 2、没有标题栏的弹出窗口,点击部分区域或者全部区域,拖动窗口。 实现方法: 1、响应鼠标按下消息,然后在鼠标移动时移动窗口。对于程序基础好的程序员,可以按这个思维很快实现,对于初学者,可能就要多调试一会儿了。这种方法实现要用到setcapture相关函数。而且窗口拖过以后会有残影,效果不是很理想.(有可能有改进方法,我没找到...) 2、给MFC发送非客户区点击消息:WM_NCLBUTTONDOWN 仔细了解一下MFC的实现,就能找到更简单的方法。 MFC把窗口分成两个部分,一部分是Client区,也叫客户区,就是我们可以在上面画画、放置子窗口等地方的区域,程序员负责这个区域的操作;另一部分叫NC区,也就是Not Client区,称为非客户区,标题栏、边框、窗口菜单等都属于非客户区。在非客户区的操作都由MFC负责。而MFC并没有独揽窗口非客户区的操作大权,程序员可以使用虚函数、窗口消息等方式影响非客户区的操作。 当鼠标在非客户区按下的时候,MFC通过发送WM_NCHITTEST消息来确定位置,并依据WM_NCHITTEST消息的响应函数返回的位置信息确定应该执行的操作。在标题栏上按下鼠标将执行拖动窗口操作。 现在我们有了另一个实现拖动窗口的方法了:响应WM_NCHITTEST消息,无论鼠标在什么位置,都返回HTCAPTION(标题栏),骗过MFC,让它来实现窗口拖动。 如果IDE好使的话,可以使用增加消息响应向导简化编程。也可以按以下方法手动添加WM_NCHITTEST的响应函数。 1)在窗口类定义中添加响应函数的声明: afx_msg UINT OnNcHitTest(CPoint point); 2)在消息响应入口表中登记响应入口,也就是在EGIN_MESSAGE_MAP和END_MESSAGE_MAP之间增加以下内容: ON_WM_NCHITTEST() 3)实现OnNcHitTest的函数体,假设类名为CXxx: UINT CXxx::OnNcHitTest(CPoint point) { return HTCAPTION; } 经过这三步,窗口应该可以拖动了,实在是有点太容易。 喜欢研究问题的朋友一定会在HTCAPTION上按一下F12,也就是去看一下HTCAPTION的定义。如果您这样做了,您一定会看到在HTCAPTION旁边还定义着很多其它的东西,向下面这样: /* * WM_NCHITTEST and MOUSEHOOKSTRUCT Mouse Position Codes */ #define HTERROR (-2) #define HTTRANSPARENT (-1) #define HTNOWHERE 0 #define HTCLIENT 1 #define HTCAPTION 2 // 我们返回的那个(苏林注) #define HTSYSMENU 3 #define HTGROWBOX 4 #define HTSIZE HTGROWBOX 。。。 如果您的响应函数中,在适当地方返回这些值,可以得到很多其它效果,如最大化、改变大小、启动帮助、关闭窗口等。
也可以直接发消息: void CTestDlg::OnLButtonDown(UINT nFlags, CPoint point) { CDialog::OnLButtonDown(nFlags, point); PostMessage (WM_NCLBUTTONDOWN , HTCAPTION , MAKELPARAM(point.x, point. y)); }
|