msdn之CRT调试

来源:互联网 发布:天津基础教育网络 编辑:程序博客网 时间:2024/05/15 12:11

http://msdn.microsoft.com/zh-cn/library/zh712wwf(v=vs.80).aspx

如果要调试使用 C 运行时库的程序,这些调试技术可能会有用。

本节内容

CRT 调试库使用

描述由 C 运行时库提供的调试支持并提供有关访问这些工具的说明。

用于报告的宏

提供有关 _RPTn 和 _RPTFn 宏(在 CRTDBG.H 中定义)的信息,它们取代了用于调试的 printf 语句。

堆分配函数的“Debug”版本

讨论堆分配函数的特殊“Debug”版本,包括:CRT 如何映射调用、显式调用它们的好处、如何避免转换、跟踪客户端块中单独的分配类型和不调用 _DEBUG 的结果。

CRT 调试堆

提供指向某些主题的链接,这些主题包括内存管理和调试堆,调试堆上的块类型,如何使用调试堆,堆状态报告函数,以及跟踪堆分配请求等。

编写调试挂钩函数

列出指向客户端块挂钩函数、分配挂钩函数、分配挂钩和 CRT 内存分配以及报告挂钩函数的链接。

1.

C 运行时库提供广泛的调试支持。若要使用 CRT 调试库之一,必须链接 /DEBUG,并用 /MDd/MTd 或 /LDd 编译。

备注

CRT 调试的主要定义和宏可在 CRTDBG.h 头文件中找到。

CRT 调试库中的函数编译时带有调试信息(/Z7、/Zd、/Zi、/ZI(调试信息格式)),不进行优化。某些函数包含断言以验证传递给它们的参数,并且提供源代码。使用此类源代码,可以单步执行 CRT 函数,以确认这些函数按预期方式工作并检查错误的参数或内存状态。(某些 CRT 技术是专有技术,不提供用于异常处理、浮点和少数其他例程的源代码。)

安装 Visual C++ 时,可以选择在硬盘上安装 C 运行时库源代码。如果不安装源代码,将需要 CD-ROM 才能单步执行 CRT 函数。

有关可以使用的各种运行时库的更多信息,请参见 C 运行时库。

2.

可以使用在 CRTDBG.H 中定义的 _RPTn 和 _RPTFn 宏替换 printf 语句进行调试。未定义 _DEBUG 时,这些宏在发布版本中自动消失,因此不必将它们括在#ifdef 内。

宏函数

_RPT0_RPT1_RPT2_RPT3_RPT4

向四个参数输出一个消息字符串和零。

对于从 _RPT1 到 _RPT4,消息字符串作为参数的 printf 样式的格式化字符串。

_RPTF0_RPTF1 _RPTF2 _RPTF4

与 _RPTn 相同,但这些宏还输出其所在的文件名和行号。

考虑下面的示例:

#ifdef _DEBUG    if ( someVar > MAX_SOMEVAR )        printf( "OVERFLOW! In NameOfThisFunc( ),               someVar=%d, otherVar=%d.\n",               someVar, otherVar );#endif

该代码将 someVar 和 otherVar 的值输出到 stdout。可以使用以下对 _RPTF2 的调用报告同样的值另加文件名和行号:

if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );

如果发现某特定应用程序需要调试报告,而 C 运行时库提供的宏不提供该报告,则可以编写专门设计的宏来符合您自己的要求。例如,可以在其中一个头文件中包含以下代码来定义名为 ALERT_IF2 的宏:

#ifndef _DEBUG                  /* For RELEASE builds */#define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)#else                           /* For DEBUG builds   */#define  ALERT_IF2(expr, msg, arg1, arg2) \    do { \        if ((expr) && \            (1 == _CrtDbgReport(_CRT_ERROR, \                __FILE__, __LINE__, msg, arg1, arg2))) \            _CrtDbgBreak( ); \    } while (0)#endif

对 ALERT_IF2 的一个调用可以执行本主题开始处的 printf 代码的所有函数:

ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ), someVar=%d, otherVar=%d.\n", someVar, otherVar );

因为可以方便地更改自定义宏,以便向不同目标报告或多或少的信息(取决于怎样更方便),所以该方法在调试要求不断发展时尤其有用。

3.

C 运行时库包含堆分配函数的特殊“Debug”版本。这些函数的名称与“Release”版本相同,并且追加了“_dbg”。本主题用 malloc 和 _malloc_dbg 作为示例,描述 CRT 函数的“Release”版本和 _dbg 版本之间的差异。

定义了 _DEBUG 后,CRT 将所有 malloc 调用映射到 _malloc_dbg。因此,不需要用 _malloc_dbg 代替 malloc 来重写代码以获得调试时的好处。

但您可能希望显式调用 _malloc_dbg。显式调用 _malloc_dbg 具有一些附加的好处:

  • 跟踪 _CLIENT_BLOCK 类型分配。

  • 存储分配请求所在的源文件和行号。

如果不希望将 malloc 调用转换为 _malloc_dbg,可以通过定义 _CRTDBG_MAP_ALLOC(这导致预处理器将对 malloc 的所有调用直接映射到 _malloc_dbg,而不是依赖 malloc 周围的包装)来获得源文件信息。

若要跟踪客户端块中各种类型的分配,必须直接调用 _malloc_dbg,并将 blockType 参数设置为 _CLIENT_BLOCK

“未”定义 _DEBUG 时,对 malloc 的调用不受妨碍,对 _malloc_dbg 的调用被解析为对 malloc 的调用,忽略 _CRTDBG_MAP_ALLOC 的定义,并且不提供与分配请求有关的源文件信息。因为 malloc 没有块类型参数,所以将对 _CLIENT_BLOCK 类型的请求作为标准分配处理。

4.

本节提供对 CRT 调试堆的详细描述。

本节内容

内存管理和调试堆

描述堆函数的“Debug”版本。这些函数解决两个最难处理的内存分配问题:改写已分配缓冲区的结尾和内存泄漏(当不再需要分配后未能释放它们)。

调试堆中的块类型

描述在调试堆中内存块所分配到的五种分配类型。出于泄漏检测和状态报告的目的,以不同方式对这些分配类型进行跟踪和报告。

调试堆

提供有关使用调试堆的信息。信息包括:哪些调用用于“Debug”版本,释放内存块时将发生什么,哪些调试功能必须从代码内部进行访问,更改_crtDbgFlag 位域以创建标志的新状态的步骤,以及一个阐释如何打开自动泄漏检测和如何关闭 _CRT_BLOCK 类型块的检查的代码示例。

C++ 中的调试堆

讨论 C++ new 和 delete 运算符的“Debug”版本和使用 _CRTDBG_MAP_ALLOC 的效果。

堆状态报告函数

描述 _CrtMemState 结构,可以使用它来捕捉堆状态的摘要快照。本主题还列出一些 CRT 函数,这些函数报告堆的状态和内容并使用这些信息来帮助检测内存泄漏和其他问题。

跟踪堆分配请求

包含用于标识出错的特定堆分配调用的方法。

4.1

程序员遇到的两种最常见而又难处理的问题是,改写已分配缓冲区的末尾以及内存泄漏(未能在不再需要某些分配后将其释放)。调试堆提供功能强大的工具来解决这类内存分配问题。

堆函数的“Debug”版本

堆函数的“Debug”版本调用“Release”版本中使用的标准版本或基版本。当请求内存块时,调试堆管理器从基堆分配略大于所请求的块的内存块,并返回指向该块中属于您的部分的指针。例如,假定应用程序包含调用:malloc( 10 )。在“Release”版本中,malloc 将调用基堆分配例程以请求分配 10 个字节。但在“Debug”版本中,malloc 将调用 _malloc_dbg,该函数接着调用基堆分配例程以请求分配 10 个字节加上大约 36 个字节的额外内存。调试堆中产生的所有内存块在单个链接列表中连接起来,按照分配时间排序。

调试堆例程分配的附加内存的用途为:存储簿记信息,存储将调试内存块链接在一起的指针,以及形成数据两侧的小缓冲区(用于捕捉已分配区域的改写)。

当前,用于存储调试堆的簿记信息的块头结构在 DBGINT.H 头文件中声明如下:

typedef struct _CrtMemBlockHeader{// Pointer to the block allocated just before this one:    struct _CrtMemBlockHeader *pBlockHeaderNext;// Pointer to the block allocated just after this one:    struct _CrtMemBlockHeader *pBlockHeaderPrev;    char *szFileName;    // File name    int nLine;           // Line number    size_t nDataSize;    // Size of user block    int nBlockUse;       // Type of block    long lRequest;       // Allocation number// Buffer just before (lower than) the user's memory:    unsigned char gap[nNoMansLandSize];} _CrtMemBlockHeader;/* In an actual memory block in the debug heap, * this structure is followed by: *   unsigned char data[nDataSize]; *   unsigned char anotherGap[nNoMansLandSize]; */

该块的用户数据区域两侧的 NoMansLand 缓冲区当前大小为 4 个字节,并用调试堆例程所使用的已知字节值填充,以验证尚未改写用户内存块限制。调试堆还用已知值填充新的内存块。如果选择在堆的链接列表中保留已释放块(如下文所述),则这些已释放块也用已知值填充。当前,所用的实际字节值如下所示:

NoMansLand (0xFD)

应用程序所用内存两侧的“NoMansLand”缓冲区当前用 0xFD 填充。

已释放块 (0xDD)

设置 _CRTDBG_DELAY_FREE_MEM_DF 标志后,调试堆的链接列表中保留未使用的已释放块当前用 0xDD 填充。

新对象 (0xCD)

分配新对象时,这些对象用 0xCD 填充。

4.2

调试堆中的每个内存块都分配以五种分配类型之一。出于泄漏检测和状态报告目的对这些类型进行不同地跟踪和报告。可以指定块的类型,方法是使用对其中一个调试堆分配函数(如 _malloc_dbg)的直接调用来分配块。调试堆中的五种内存块类型(在 _CrtMemBlockHeader 结构的 nBlockUse 成员中设置)如下所示:

_NORMAL_BLOCK

对 malloc 或 calloc 的调用将创建“普通”块。如果打算只使用“普通”块而不需要“客户端”块,则可能想要定义 _CRTDBG_MAP_ALLOC,它导致所有堆分配调用映射到它们在“Debug”版本中的调试等效项。这将允许将关于每个分配调用的文件名和行号信息存储到对应的块头中。

_CRT_BLOCK

由许多运行时库函数内部分配的内存块被标记为 CRT 块,以便可以单独处理这些块。结果,泄漏检测和其他操作不需要受这些块影响。分配永不可以分配、重新分配或释放任何 CRT 类型的块。

_CLIENT_BLOCK

出于调试目的,应用程序可以专门跟踪一组给定的分配,方法是使用对调试堆函数的显式调用将它们作为该类型的内存块进行分配。例如,MFC 以“客户端”块类型分配所有的 CObjects;其他应用程序则可能在“客户端”块中保留不同的内存对象。还可以指定“客户端”块的子类型以获得更大的跟踪粒度。若要指定“客户端”块子类型,请将该数字向左移 16 位,并将它与 _CLIENT_BLOCK 进行 OR 运算。例如:

#define MYSUBTYPE 4freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

客户端提供的挂钩函数(用于转储在“客户端”块中存储的对象)可以使用 _CrtSetDumpClient 进行安装,然后,每当调试函数转储“客户端”块时均会调用该挂钩函数。同样,对于调试堆中的每个“客户端”块,可以使用 _CrtDoForAllClientObjects 来调用应用程序提供的给定函数。

_FREE_BLOCK

通常,所释放的块将从列表中移除。为了检查并未仍在向已释放的内存写入数据,或为了模拟内存不足情况,可以选择在链接列表上保留已释放块,将其标记为“可用”,并用已知字节值(当前为 0xDD)填充。

_IGNORE_BLOCK

有可能在一段时间内关闭调试堆操作。在该时间段内,内存块保留在列表上,但被标记为“忽略”块。

若要确定给定块的类型和子类型,请使用 _CrtReportBlockType 函数以及 _BLOCK_TYPE 和 _BLOCK_SUBTYPE 宏。宏的定义(在 crtdbg.h 中)如下所示:

#define _BLOCK_TYPE(block)          (block & 0xFFFF)#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)
4.3

许多调试堆功能必须从代码内访问。下一节描述其中一些功能以及如何使用这些功能。

_CrtCheckMemory

例如,可以使用对 _CrtCheckMemory 的调用来检查堆在任意点的完整性。该函数检查堆中的每个内存块,验证内存块头信息有效,并确认尚未修改缓冲区。

_CrtSetDbgFlag

可以使用内部标志 _crtDbgFlag 来控制调试堆跟踪分配的方式,该标志可使用 _CrtSetDbgFlag 函数进行读取和设置。通过更改该标志,可以指示调试堆在程序退出时检查内存泄漏,并报告检测到的所有泄漏。类似地,可以指定不将已释放的内存块从链接列表移除,以模拟内存不足情况。当检查堆时,将完全检查这些已释放的块,以确保它们未受打扰。

_crtDbgFlag 标志包含下列位域:

位域默认值说明

_CRTDBG_ALLOC_MEM_DF

On

打开调试分配。当该位为 off 时,分配仍链接在一起,但它们的块类型为 _IGNORE_BLOCK

_CRTDBG_DELAY_FREE_MEM_DF

Off

防止实际释放内存,与模拟内存不足情况相同。当该位为 on 时,已释放块保留在调试堆的链接列表中,但标记为 _FREE_BLOCK,并用特殊字节值填充。

_CRTDBG_CHECK_ALWAYS_DF

Off

导致每次分配和释放时均调用 _CrtCheckMemory。这将减慢执行,但可快速捕捉错误。

_CRTDBG_CHECK_CRT_DF

Off

导致将标记为 _CRT_BLOCK 类型的块包括在泄漏检测和状态差异操作中。当该位为 off 时,在这些操作期间将忽略由运行时库内部使用的内存。

_CRTDBG_LEAK_CHECK_DF

Off

导致在程序退出时通过调用 _CrtDumpMemoryLeaks 来执行泄漏检查。如果应用程序未能释放其所分配的所有内存,将生成错误报告。

4.4

对于堆函数(如 mallocfreecallocreallocnew 和 delete)的所有调用均解析为这些函数在调试堆中运行的调试版本。当释放内存块时,调试堆自动检查已分配区域两侧的缓冲区的完整性,如果发生改写,将发出错误报告。

使用调试堆

  • 用 C 运行时库的调试版本链接应用程序的调试版本。

更改一个或多个 _crtDbgFlag 位域并创建标志的新状态

  1. 在 newFlag 参数设置为 _CRTDBG_REPORT_FLAG 的情况下调用 _CrtSetDbgFlag(以获得当前的 _crtDbgFlag 状态),并在一个临时变量中存储返回值。

  2. 通过对带相应位屏蔽的临时变量(在应用程序代码中由清单常数显示)进行 OR 运算(按位|符号)来打开任何位。

  3. 关闭其他位,对该变量与相应位屏蔽的 NOT(按位 ~ 符号)进行 AND 运算(按位 & 符号)。

  4. 在 newFlag 参数设置为临时变量中存储的值的情况下调用 _CrtSetDbgFlag,以创建 _crtDbgFlag 的新状态。

示例

例如,下列代码行打开自动泄漏检测,关闭检查 _CRT_BLOCK 类型的块:

// Get current flagint tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );// Turn on leak-checking bit.tmpFlag |= _CRTDBG_LEAK_CHECK_DF;// Turn off CRT block checking bit.tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;// Set flag to the new value._CrtSetDbgFlag( tmpFlag );
4.5

在 C++ 中,可以直接调用 new 运算符的“调试”版本,或者创建可在调试模式中替换 new 运算符的宏,如下面的示例所示。

替换 new 运算符

/* MyDbgNew.h Defines global operator new to allocate from client blocks*/#ifdef _DEBUG   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)#else   #define DEBUG_CLIENTBLOCK#endif // _DEBUG/* MyApp.cpp    Compile options needed: /Zi /D_DEBUG /MLd *            or use a *      Default Workspace for a Console Application to *      build a Debug version*/#include "crtdbg.h"#include "mydbgnew.h"#ifdef _DEBUG#define new DEBUG_CLIENTBLOCK#endifint main( )   {    char *p1;    p1 =  new char[40];    _CrtMemDumpAllObjectsSince( NULL );}

delete 运算符的“Debug”版本可用于所有块类型,并且编译“Release”版本时程序中不需要任何更改。

4.6

有几个函数可报告给定时刻调试堆的内容。

_CrtMemState

若要捕获给定时刻堆状态的摘要快照,请使用 CRTDBG.H 中定义的 _CrtMemState 结构:

typedef struct _CrtMemState{    // Pointer to the most recently allocated block:    struct _CrtMemBlockHeader * pBlockHeader;    // A counter for each of the 5 types of block:    size_t lCounts[_MAX_BLOCKS];    // Total bytes allocated in each block type:    size_t lSizes[_MAX_BLOCKS];    // The most bytes allocated at a time up to now:    size_t lHighWaterCount;    // The total bytes allocated at present:    size_t lTotalCount;} _CrtMemState;

该结构保存指向调试堆的链接列表中的第一个(最近分配的)块的指针。然后,它在两个数组中记录列表中每种类型的内存块(_NORMAL_BLOCK_CLIENT_BLOCK_FREE_BLOCK 等等)的个数,以及每种类型的块中分配的字节数。最后,它记录到该点为止堆中总共分配的最大字节数以及当前分配的字节数。

其他 CRT 报告函数

下列函数报告堆的状态和内容,并使用这些信息帮助检测内存泄漏及其他问题:

函数说明

_CrtMemCheckpoint

在应用程序提供的 _CrtMemState 结构中保存堆的快照。

_CrtMemDifference

比较两个内存状态结构,在第三个状态结构中保存二者之间的差异,如果两个状态不同,则返回 TRUE

_CrtMemDumpStatistics

转储给定的 _CrtMemState 结构。该结构可能包含给定时刻调试堆状态的快照或两个快照之间的差异。

_CrtMemDumpAllObjectsSince

转储自对堆拍了给定快照以来或从执行开始以来所分配的所有对象的信息。如果已经使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtMemDumpAllObjectsSince 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。

_CrtDumpMemoryLeaks

确定自程序开始执行以来是否发生过内存泄漏,如果发生过,则转储所有已分配对象。如果已使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtDumpMemoryLeaks 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。

4.7

尽管查明在其中执行断言或报告宏的源文件名和行号对于定位问题原因常常很有用,对于堆分配函数却可能不是这样。虽然可在应用程序的逻辑树中的许多适当点插入宏,但分配经常隐藏在特殊例程中,该例程会在很多不同时刻从很多不同位置进行调用。问题通常并不在于如何确定哪行代码进行了错误分配,而在于如何确定该行代码进行的上千次分配中的哪一次是错误分配以及原因。

唯一分配请求编号和 _crtBreakAlloc

标识发生错误的特定堆分配调用的最简单方法是利用与调试堆中的每个块关联的唯一分配请求编号。当其中一个转储函数报告某块的有关信息时,该分配请求编号将括在大括号中(例如“{36}”)。

知道某个错误分配块的分配请求编号后,可以将该编号传递给 _CrtSetBreakAlloc 以创建一个断点。执行将恰在分配该块以前中断,您可以向回追踪以确定哪个例程执行了错误调用。为避免重新编译,可以在调试器中完成同样的操作,方法是将 _crtBreakAlloc 设置为所感兴趣的分配请求编号。

创建分配例程的“Debug”版本

略微复杂的方法是创建您自己的分配例程的“Debug”版本,等同于堆分配函数的 _dbg 版本。然后,可以将源文件和行号参数传递给基础堆分配例程,并能立即看到错误分配的出处。

例如,假定您的应用程序包含与下面类似的常用例程:

int addNewRecord(struct RecStruct * prevRecord,                 int recType, int recAccess){    // ...code omitted through actual allocation...     if ((newRec = malloc(recSize)) == NULL)    // ... rest of routine omitted too ... }

在头文件中,可以添加如下代码:

#ifdef _DEBUG#define  addNewRecord(p, t, a) \            addNewRecord(p, t, a, __FILE__, __LINE__)#endif

接下来,可以如下更改记录创建例程中的分配:

int addNewRecord(struct RecStruct *prevRecord,                int recType, int recAccess#ifdef _DEBUG               , const char *srcFile, int srcLine#endif    ){    /* ... code omitted through actual allocation ... */    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,            srcFile, scrLine)) == NULL)    /* ... rest of routine omitted too ... */}

在其中调用 addNewRecord 的源文件名和行号将存储在产生的每个块中(这些块是在调试堆中分配的),并将在检查该块时进行报告。

5.

本节描述了您可以编写的自定义调试挂钩函数,它允许您在调试器的正常处理中将代码插入某些预定义的点中。

本节内容

客户端块挂钩函数

提供有关编写某些函数的指导和原型,这些函数验证或报告正存储在 _CLIENT_BLOCK 块中的数据的内容。

分配挂钩函数

定义分配挂钩函数,探讨它的不同用法,指出限制并提供原型。

分配挂钩和 CRT 内存分配

描述分配挂钩函数在调用分配内部内存的 C 运行时库函数时,在显式忽略 _CRT_BLOCK 块方面所受的限制。本主题还用示例列出了分配挂钩不忽略_CRT_BLOCK 块的后果,并描述如何更改默认分配挂钩函数 CrtDefaultAllocHook

报告挂钩函数

讨论 _CrtSetReportHook,可以使用它筛选报告以集中于特定的分配类型。本主题还提供原型。

5.1

如果想要验证或报告存储在 _CLIENT_BLOCK 块中的数据的内容,可以专为此目的编写函数。如同 CRTDBG.H 中所定义的,所编写的函数必须有与下面类似的原型:

void YourClientDump(void *, size_t)

换句话说,您的挂钩函数应接受一个 void 指针(指向分配块的起始),以及一个 size_t 类型值(指示分配大小),并返回 void。除此之外,其内容由您决定。

使用 _CrtSetDumpClient 安装了挂钩函数后,每次转储 _CLIENT_BLOCK 块时都将调用该挂钩函数。然后,可以使用 _CrtReportBlockType 获取有关转储块的类型或子类型的信息。

您传递给 _CrtSetDumpClient 的指向函数的指针是 _CRT_DUMP_CLIENT 类型,如 CRTDBG.H 中所定义:

typedef void (__cdecl *_CRT_DUMP_CLIENT)   (void *, size_t);

5.2

每次分配、重新分配或释放内存时都会调用分配挂钩函数(挂钩函数使用 _CrtSetAllocHook 安装)。该类型的挂钩可用于很多不同用途。例如,可用它测试应用程序处理内存不足情况的方式,检查分配模式,或记录分配信息以供将来分析。

Note注意

注意有关在分配挂钩函数中使用 C 运行时库函数的限制,详见“分配挂钩和 C 运行时内存分配”中的说明。

分配挂钩函数应有类似下面的原型:

int YourAllocHook(int nAllocType, void *pvData,        size_t nSize, int nBlockUse, long lRequest,        const unsigned char * szFileName, int nLine )

传递给 _CrtSetAllocHook 的指针为 _CRT_ALLOC_HOOK 类型,如 CRTDBG.H 中所定义:

typedef int (__cdecl * _CRT_ALLOC_HOOK)    (int, void *, size_t, int, long, const unsigned char *, int);

当运行时库调用您的挂钩时,nAllocType 参数指示将执行哪种分配操作(_HOOK_ALLOC_HOOK_REALLOC 或 _HOOK_FREE)。在释放或重新分配的情况下,pvData 包含指向将释放块的用户主题的指针。但在分配情况下,该指针为空,因为分配尚未发生。剩余的参数包含所讨论分配的大小、其块类型、与它关联的顺序请求编号,以及指向在其中进行分配的文件名和行号的指针(如果有)。挂钩函数执行了其作者需要的所有分析及其他任务以后,必须返回 TRUE(指示分配操作可以继续)或 FALSE(指示分配操作应失败)。该类型的简单挂钩可以检查迄今为止分配的内存量,如果该数量超出小限制,则返回 FALSE。然后应用程序将经历分配错误,这种错误通常只会在可用内存极为不足时发生。较复杂的挂钩可以跟踪分配模式,分析内存使用,或在特定情况发生时进行报告。

5.3

对分配挂钩函数的一个非常重要的限制是,当它们调用任何分配内部内存的 C 运行时库函数时,必须显式忽略 _CRT_BLOCK 块(由 C 运行时库函数内部进行的内存分配)。可通过在分配挂钩函数起始包括如下代码来忽略 _CRT_BLOCK 块:

    if ( nBlockUse == _CRT_BLOCK )        return( TRUE );

如果您的分配挂钩未忽略 _CRT_BLOCK 块,那么挂钩中调用的任何 C 运行时库函数都会使程序陷入无穷循环。例如,printf 执行内部分配。如果挂钩代码调用printf,那么产生的分配将导致再次调用挂钩,而挂钩又再次调用 printf,如此继续直到堆栈溢出。如果需要报告 _CRT_BLOCK 分配操作,回避该限制的一种方法是,使用 Windows API 函数而不是 C 运行时函数来进行格式化和输出。因为 Windows API 不使用 C 运行时库堆,所以它们不会使您的分配挂钩陷入无穷循环。

如果检查运行时库源文件,将看到默认分配挂钩函数 CrtDefaultAllocHook(只返回 TRUE)位于它自己的单独文件 DBGHOOK.C 中。如果希望甚至为运行时启动代码(在您应用程序的 main 函数之前执行)进行的分配调用您的分配挂钩,可以用您自己的函数替换该默认函数,而不使用 _CrtSetAllocHook。

5.4

每次 _CrtDbgReport 生成调试报告时都会调用报告挂钩函数(使用 _CrtSetReportHook 安装)。可以使用报告挂钩函数以及其他项筛选报告以集中于特定类型的分配。报告挂钩函数应具有如下原型:

int YourReportHook(int nRptType, char *szMsg, int *retVal);

传递给 _CrtSetReportHook 的指针为 _CRT_REPORT_HOOK 类型,如 CRTDBG.H 中所定义:

typedef int (__cdecl *_CRT_REPORT_HOOK)(int, char *, int *);

当运行时库调用您的挂钩函数时,nRptType 参数包含报告类别(_CRT_WARN_CRT_ERROR 或 _CRT_ASSERT),szMsg 包含指向完全汇编的报告消息字符串的指针,而 retVal 指定 _CrtDbgReport 应在生成报告以后继续正常执行还是启动调试器。(retVal 值为零继续执行,值为 1 启动调试器。)

如果挂钩完全处理了所讨论的消息,因而不需要进一步的报告,那么应返回 TRUE。如果返回 FALSE_CrtDbgReport 将以正常方式报告消息。


0 0
原创粉丝点击