CRT 调试功能来检测内存泄漏.docx

上传人:b****3 文档编号:5209586 上传时间:2022-12-14 格式:DOCX 页数:34 大小:243.47KB
下载 相关 举报
CRT 调试功能来检测内存泄漏.docx_第1页
第1页 / 共34页
CRT 调试功能来检测内存泄漏.docx_第2页
第2页 / 共34页
CRT 调试功能来检测内存泄漏.docx_第3页
第3页 / 共34页
CRT 调试功能来检测内存泄漏.docx_第4页
第4页 / 共34页
CRT 调试功能来检测内存泄漏.docx_第5页
第5页 / 共34页
点击查看更多>>
下载资源
资源描述

CRT 调试功能来检测内存泄漏.docx

《CRT 调试功能来检测内存泄漏.docx》由会员分享,可在线阅读,更多相关《CRT 调试功能来检测内存泄漏.docx(34页珍藏版)》请在冰豆网上搜索。

CRT 调试功能来检测内存泄漏.docx

CRT调试功能来检测内存泄漏

VC++6.0中如何使用CRT调试功能来检测内存泄漏

作者:

JerryZ

下载例子源代码

  最近看了周星星Blog中的一篇文章:

“VC++6.0中内存泄漏检测”,受益匪浅,便运行其例子代码想看看Output窗口中的输出结果,可惜怎么弄其输出都不是预期的东西,郁闷了半天,便到水坛里找到周星星,请求他指点一、二,然而未果。

没有办法,最后我一头栽进MSDN库狂搜了一把,功夫不负有心人,我搜出很多有关这方面的资料,没过多久我便基本上就找到了答案......

  首先,检测内存泄漏的基本工具是调试器和CRT调试堆函数。

为了使用调试堆函数,必须在要检测内存泄漏和调试的程序中添加下面的语句:

 #define_CRTDBG_MAP_ALLOC#include#include#include"debug_new.h"

  MSDN如是说:

“必须保证上面声明的顺序,如果改变了顺序,可能不能正常工作。

”至于这是为什么,我们不得而知。

MS的老大们经常这样故弄玄虚。

  针对非MFC程序,再加上周星星的头文件:

debug_new.h,当然如果不加这一句,也能检测出内存泄漏,但是你无法确定在哪个源程序文件中发生泄漏。

Output输出只告诉你在crtsdb.h中的某个地方有内存泄漏。

我测试时REG_DEBUG_NEW没有起作用。

加不加这个宏都可以检测出发生内存分配泄漏的文件。

  其次,一旦添加了上面的声明,你就可以通过在程序中加入下面的代码来报告内存泄漏信息了:

_CrtDumpMemoryLeaks();

  这就这么简单。

我在周星星的例子代码中加入这些机关后,在VC++调试会话(按F5调试运行)Output窗口的Debug页便看到了预期的内存泄漏dump。

该dump形式如下:

Detectedmemoryleaks!

Dumpingobjects->c:

/ProgramFiles/.../include/crtdbg.h(552):

{45}normalblockat0x00441BA0,2byteslong.Data:

4142c:

/ProgramFiles/.../include/crtdbg.h(552):

{44}normalblockat0x00441BD0,33byteslong.Data:

004300CDCDCDCDCDCDCDCDCDCDCDCDCDc:

/ProgramFiles/.../include/crtdbg.h(552):

{43}normalblockat0x00441C20,40byteslong.Data:

E8014300160000000000000000000000Objectdumpcomplete.

更具体的细节请参考本文附带的源代码文件。

  下面是我看过MSDN资料后,针对“如何使用CRT调试功能来检测内存泄漏?

”的问题进行了一番编译和整理,希望对大家有用。

如果你的英文很棒,那就不用往下看了,建议直接去读MSDN库中的技术原文。

  C/C++编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:

“最大的长处也可能成为最大的弱点”,那么C/C++应用程序正好印证了这句话。

在C/C++应用程序开发过程中,动态分配的内存处理不当是最常见的问题。

其中,最难捉摸也最难检测的错误之一就是内存泄漏,即未能正确释放以前分配的内存的错误。

偶尔发生的少量内存泄漏可能不会引起我们的注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种各样的征兆:

从性能不良(并且逐渐降低)到内存完全耗尽。

更糟的是,泄漏的程序可能会用掉太多内存,导致另外一个程序垮掉,而使用户无从查找问题的真正根源。

此外,即使无害的内存泄漏也可能殃及池鱼。

  幸运的是,VisualStudio调试器和C运行时(CRT)库为我们提供了检测和识别内存泄漏的有效方法。

下面请和我一起分享收获——如何使用CRT调试功能来检测内存泄漏?

1.如何启用内存泄漏检测机制?

o使用_CrtSetDbgFlag

o设置CRT报告模式

2.解释内存块类型

3.如何在内存分配序号处设置断点?

4.如何比较内存状态?

5.结论

如何启用内存泄漏检测机制?

  VC++IDE的默认状态是没有启用内存泄漏检测机制的,也就是说即使某段代码有内存泄漏,调试会话的Output窗口的Debug页不会输出有关内存泄漏信息。

你必须设定两个最基本的机关来启用内存泄漏检测机制。

一是使用调试堆函数:

#define_CRTDBG_MAP_ALLOC#include#include

注意:

#include语句的顺序。

如果更改此顺序,所使用的函数可能无法正确工作。

  通过包含crtdbg.h头文件,可以将malloc和free函数映射到其“调试”版本_malloc_dbg和_free_dbg,这些函数会跟踪内存分配和释放。

此映射只在调试(Debug)版本(也就是要定义_DEBUG)中有效。

发行版本(Release)使用普通的malloc和free函数。

  #define语句将CRT堆函数的基础版本映射到对应的“调试”版本。

该语句不是必须的,但如果没有该语句,那么有关内存泄漏的信息会不全。

二是在需要检测内存泄漏的地方添加下面这条语句来输出内存泄漏信息:

_CrtDumpMemoryLeaks();

  当在调试器下运行程序时,_CrtDumpMemoryLeaks将在Output窗口的Debug页中显示内存泄漏信息。

比如:

Detectedmemoryleaks!

Dumpingobjects->C:

/Temp/memleak/memleak.cpp(15):

{45}normalblockat0x00441BA0,2byteslong.Data:

4142c:

/programfiles/microsoftvisualstudio/vc98/include/crtdbg.h(552):

{44}normalblockat0x00441BD0,33byteslong.Data:

004300CDCDCDCDCDCDCDCDCDCDCDCDCDc:

/programfiles/microsoftvisualstudio/vc98/include/crtdbg.h(552):

{43}normalblockat0x00441C20,40byteslong.Data:

08024300160000000000000000000000Objectdumpcomplete.

如果不使用#define_CRTDBG_MAP_ALLOC语句,内存泄漏的输出是这样的:

Detectedmemoryleaks!

Dumpingobjects->{45}normalblockat0x00441BA0,2byteslong.Data:

4142{44}normalblockat0x00441BD0,33byteslong.Data:

004300CDCDCDCDCDCDCDCDCDCDCDCDCD{43}normalblockat0x00441C20,40byteslong.Data:

C0014300160000000000000000000000Objectdumpcomplete.

  根据这段输出信息,你无法知道在哪个源程序文件里发生了内存泄漏。

下面我们来研究一下输出信息的格式。

第一行和第二行没有什么可说的,从第三行开始:

xx}:

花括弧内的数字是内存分配序号,本文例子中是{45},{44},{43};block:

内存块的类型,常用的有三种:

normal(普通)、client(客户端)或CRT(运行时);本文例子中是:

normalblock;用十六进制格式表示的内存位置,如:

at0x00441BA0等;以字节为单位表示的内存块的大小,如:

32byteslong;前16字节的内容(也是用十六进制格式表示),如:

Data:

4142等;

  仔细观察不难发现,如果定义了_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,即普通块和客户端块。

∙normalblock(普通块):

这是由你的程序分配的内存。

∙clientblock(客户块):

这是一种特殊类型的内存块,专门用于MFC程序中需要析构函数的对象。

MFCnew操作符视具体情况既可以为所创建的对象建立普通块,也可以为之建立客户块。

∙CRTblock(CRT块):

是由CRunTimeLibrary供自己使用而分配的内存块。

由CRT库自己来管理这些内存的分配与释放,我们一般不会在内存泄漏报告中发现CRT内存泄漏,除非程序发生了严重的错误(例如CRT库崩溃)。

除了上述的类型外,还有下面这两种类型的内存块,它们不会出现在内存泄漏报告中:

∙freeblock(空闲块):

已经被释放(free)的内存块。

∙Ignoreblock(忽略块):

这是程序员显式声明过不要在内存泄漏报告中出现的内存块。

如何在内存分配序号处设置断点?

  在内存泄漏报告中,的文件名和行号可告诉分配泄漏的内存的代码位置,但仅仅依赖这些信息来了解完整的泄漏原因是不够的。

因为一个程序在运行时,一段分配内存的代码可能会被调用很多次,只要有一次调用后没有释放内存就会导致内存泄漏。

为了确定是哪些内存没有被释放,不仅要知道泄漏的内存是在哪里分配的,还要知道泄漏产生的条件。

这时内存分配序号就显得特别有用——这个序号就是文件名和行号之后的花括弧里的那个数字。

  例如,在本文例子代码的输出信息中,“45”是内存分配序号,意思是泄漏的内存是你程序中分配的第四十五个内存块:

Detectedmemoryleaks!

Dumpingobjects->C:

/Temp/memleak/memleak.cpp(15):

{45}normalblockat0x00441BA0,2byteslong.Data:

4142......Objectdumpcomplete.

  CRT库对程序运行期间分配的所有内存块进行计数,包括由CRT库自己分配的内存和其它库(如MFC)分配的内存。

因此,分配序号为N的对象即为程序中分配的第N个对象,但不一定是代码分配的第N个对象。

(大多数情况下并非如此。

  这样的话,你便可以利用分配序号在分配内存的位置设置一个断点。

方法是在程序起始附近设置一个位置断点。

当程序在该点中断时,可以从QuickWatch(快速监视)对话框或Watch(监视)窗口设置一个内存分配断点:

  例如,在Watch窗口中,在Name栏键入下面的表达式:

_crtBreakAlloc

如果要使用CRT库的多线程DLL版本(/MD选项),那么必须包含上下文操作符,像这样:

{,,msvcrtd.dll}_crtBreakAlloc

  现在按下回车键,调试器将计算该值并把结果放入Value栏。

如果没有在内存分配点设置任何断点,该值将为–1。

  用你想要在其位置中断的内存分配的分配序号替换Value栏中的值。

例如输入45。

这样就会在分配序号为45的地方中断。

  在所感兴趣的内存分配处设置断点后,可以继续调试。

这时,运行程序时一定要小心,要保证内存块分配的顺序不会改变。

当程序在指定的内存分配处中断时,可以查看CallStack(调用堆栈)窗口和其它调试器信息以确定分配内存时的情况。

如果必要,可以从该点继续执行程序,以查看对象发生了什么情况,或许可以确定未正确释放对象的原因。

  尽管通常在调试器中设置内存分配断点更方便,但如果愿意,也可在代码中设置这些断点。

为了在代码中设置一个内存分配断点,可以增加这样一行(对于第四十五个内存分配):

_crtBreakAlloc=45;

你还可以使用有相同效果的_CrtSetBreakAlloc函数:

_CrtSetBreakAlloc(45);

如何比较内存状态?

  定位内存泄漏的另一个方法就是在关键点获取应用程序内存状态的快照。

CRT库提供了一个结构类型_CrtMemState。

你可以用它来存储内存状态的快照:

_CrtMemStates1,s2,s3;

  若要获取给定点的内存状态快照,可以向_CrtMemCheckpoint函数传递一个_CrtMemState结构。

该函数用当前内存状态的快照填充此结构:

_CrtMemCheckpoint(&s1);

  通过向_CrtMemDumpStatistics函数传递_CrtMemState结构,可以在任意地方dump该结构的内容:

_CrtMemDumpStatistics(&s1);

该函数输出如下格式的dump内存分配信息:

0bytesin0FreeBlocks.75bytesin3NormalBlocks.5037bytesin41CRTBlocks.0bytesin0IgnoreBlocks.0bytesin0ClientBlocks.Largestnumberused:

5308bytes.Totalallocations:

7559bytes.

  若要确定某段代码中是否发生了内存泄漏,可以通过获取该段代码之前和之后的内存状态快照,然后使用_CrtMemDifference比较这两个状态:

_CrtMemCheckpoint(&s1);//获取第一个内存状态快照//在这里进行内存分配_CrtMemCheckpoint(&s2);//获取第二个内存状态快照//比较两个内存快照的差异if(_CrtMemDifference(&s3,&s1,&s2))_CrtMemDumpStatistics(&s3);//dump差异结果

  顾名思义,_CrtMemDifference比较两个内存状态(前两个参数),生成这两个状态之间差异的结果(第三个参数)。

在程序的开始和结尾放置_CrtMemCheckpoint调用,并使用_CrtMemDifference比较结果,是检查内存泄漏的另一种方法。

如果检测到泄漏,则可以使用_CrtMemCheckpoint调用通过二进制搜索技术来分割程序和定位泄漏。

结论

  尽管VC++具有一套专门调试MFC应用程序的机制,但本文上述讨论的内存分配很简单,没有涉及到MFC对象,所以这些内容同样也适用于MFC程序。

在MSDN库中可以找到很多有关VC++调试方面的资料,如果你能善用MSDN库,相信用不了多少时间你就有可能成为调试高手。

本人水平不高,谬误在所难免,请大家拍砖,不要客气。

顺祝大家圣诞快乐!

JerryZ于2004年平安夜,

 

调试方法和技巧

作者:

非凡

便于调试的代码风格:

1.不用全局变量

2.所有变量都要初始化,成员变量在构造函数中初始化

3.尽量使用const

4.详尽的注释

VC++编译选项:

1.总是使用/W4警告级别

2.在调试版本里总是使用/GZ编译选项,用来发现在Release版本中才有的错误

3.没有警告的编译:

保证在编译后没有任何警告,但是在消除警告前要进行仔细检查

调试方法:

1、使用Assert(原则:

尽量简单)

  assert只在debug下生效,release下不会被编译。

例子:

char*strcpy(char*dest,char*source){assert(source!

=0);assert(dest!

=0);char*returnstring=dest;while((*dest++=*source++)!

=‘/0’){;}returnreturnstring;}

2、防御性的编程

例子:

char*strcpy(char*dest,char*source){if(source==0){assert(false);reutrn0;}if(dest==0){assert(false);return0;}char*returnstring=dest;while((*dest++=*source++)!

=‘/0’){;}returnreturnstring;}

3、使用Trace

以下的例子只能在debug中显示,

例子:

a)、TRACE

CStringcsTest=“test”;TRACE(“CStringis%s/n”,csTest);

b)、ATLTRACE

c)、afxDump

CTimetime=CTime:

:

GetCurrentTime();#ifdef_DEBUGafxDump<

4、用GetLastError来检测返回值,通过得到错误代码来分析错误原因

5、把错误信息记录到文件中

异常处理

  程序设计时一定要考虑到异常如何处理,当错误发生后,不应简单的报告错误并退出程序,应当尽可能的想办法恢复到出错前的状态或者让程序从头开始运行,并且对于某些错误,应该能够容错,即允许错误的存在,但是程序还是能够正常完成任务。

调试技巧

1、VC++中F5进行调试运行

a)、在outputDebug窗口中可以看到用TRACE打印的信息

b)、CallStack窗口中能看到程序的调用堆栈

2、当Debug版本运行时发生崩溃,选择retry进行调试,通过看CallStack分析出错的位置及原因

3、使用映射文件调试

a)、创建映射文件:

Projectsettings中link项,选中Generatemapfile,输出程序代码地址:

/MAPINFO:

LINES,得到引出序号:

/MAPINFO:

EXPORTS。

b)、程序发布时,应该把所有模块的映射文件都存档。

c)、查看映射文件:

见”通过崩溃地址找出源代码的出错行”文件。

4、可以调试的Release版本

  Projectsettings中C++项的DebugInfo选择为ProgramDatabase,Link项的Debug中选择DebugInfo和Microsoftformat。

5、查看API的错误码,在watch窗口输入@err可以查看或者@err,hr,其中”,hr”表示错误码的说明。

6、SetNextStatement:

该功能可以直接跳转到指定的代码行执行,一般用来测试异常处理的代码。

7、调试内存变量的变化:

当内存发生变化时停下来。

常见错误

1、在函数返回的时候程序崩溃的原因

a)、写自动变量越界

b)、函数原型不匹配

2、MFC

a)、使用错误的函数原型处理用户定义消息

正确的函数原型为:

afx_msgLRESULTOnMyMessage(WPARAMwParam,LPARAMlParam);

3、谨慎使用TerminateThread:

使用TerminateThread会造成资源泄漏,不到万不得已,不要使用。

4、使用_beginthreadex,不要使用CreateThread来常见线程。

参考资料:

《Windows程序调试》

功能强大的vc6调试器

作者:

yy2better

  要成为一位优秀的软件工程师,调试能力必不可缺。

本文将较详细介绍VC6调试器的主要用法。

  windows平台的调试器主要分为两大类:

  1用户模式(user-mode)调试器:

它们都基于win32DebuggingAPI,有使用方便的界面,主要用于调试用户模式下的应用程序。

这类调试器包括VisualC++调试器、WinDBG、BoundChecker、BorlandC++Builder调试器、NTSD等。

  2内核模式(kernel-mode)调试器:

内核调试器位于CPU和操作系统之间,一旦启动,操作系统也会中止运行,主要用于调试驱动

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1