使用 C++ 断言语句进行调试

您所在的位置:网站首页 vc线路 使用 C++ 断言语句进行调试

使用 C++ 断言语句进行调试

2024-03-12 06:18| 来源: 网络整理| 查看: 265

C/C++ 断言 项目 05/08/2023

断言语句指定你预期程序中的某个点为 true 的条件。 如果该条件不为 true,则断言失败,程序执行中断并显示“断言失败”对话框。

Visual Studio 支持基于以下构造的 C++ 断言语句:

MFC 程序的 MFC 断言。

使用 ATL 的程序的 ATLASSERT。

使用 C 运行时库的程序的 CRT 断言。

其他 C/C++ 程序的 ANSI assert 函数。

可以使用断言捕获逻辑错误、检查操作的结果以及测试应该已处理的错误条件。

本主题内容

断言的工作方式

调试和发布生成中的断言

使用断言的副作用

CRT 断言

MFC 断言

MFC ASSERT_VALID 和 CObject::AssertValid

AssertValid 的限制

使用断言

捕获逻辑错误

检查结果

查找未处理的错误

断言的工作方式

当调试器由于 MFC 或 C 运行时库断言而暂停时,如果源可用,则它定位到源文件中的断言发生点。 断言消息显示在输出窗口以及“断言失败”对话框中。 如果要保存断言消息以供将来参考,可以将断言消息从“输出”窗口复制到某个文本窗口。 输出窗口可能还包含其他错误信息。 请仔细检查这些消息,因为它们提供了断言失败原因的线索。

在开发过程中使用断言检测错误。 作为一种规则,对每个假设都使用一个断言。 例如,如果假设某个参数不为 NULL,则使用断言测试该假设。

在本主题中

调试和发布生成中的断言

断言语句仅在定义了 _DEBUG 时才进行编译。 否则,编译器会将断言视为 null 语句。 因此,断言语句不会对最终发布程序施加开销或性能成本,使你可以避免使用 #ifdef 指令。

使用断言的副作用

将断言添加到代码时,确保断言没有副作用。 例如,考虑以下修改 nM 值的断言:

ASSERT(nM++ > 0); // Don't do this!

由于不会在程序的发布版本中计算 ASSERT 表达式,因此 nM 在调试和发布版本中具有不同的值。 若要避免 MFC 中出现此问题,可以使用 VERIFY 宏而不是 ASSERT。 VERIFY 会在所有版本中都计算表达式,但不检查发行版本中的结果。

请特别注意在断言语句中使用函数调用,因为计算函数可能会产生意外的副作用。

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects VERIFY ( myFnctn(0)==1 ) // safe

VERIFY 在调试版本和发布版本都会调用 myFnctn,以便它可以使用。 但是,使用 VERIFY 会在发布版本中施加不必要的函数调用开销。

在本主题中

CRT 断言

CRTDBG.H 头文件定义 _ASSERT 和 _ASSERTE 宏用于断言检查。

宏 结果 _ASSERT 如果指定表达式的计算结果为 FALSE,则为 _ASSERT 的文件名和行号。 _ASSERTE 与 _ASSERT 相同,再加上进行断言的表达式的字符串表示形式。

_ASSERTE 功能更强大,因为它会报告结果为 FALSE 的断言表达式。 这可能足以确定问题,而无需参阅源代码。 但是,对于使用 _ASSERTE 进行断言的每个表达式,应用程序的调试版本都将包含一个字符串常数。 如果使用许多 _ASSERTE 宏,则这些字符串表达式会占用大量内存。 如果这证明有问题,请使用 _ASSERT 节省内存。

定义 _DEBUG 时,_ASSERTE 宏的定义如下:

#define _ASSERTE(expr) \ do { \ if (!(expr) && (1 == _CrtDbgReport( \ _CRT_ASSERT, __FILE__, __LINE__, #expr))) \ _CrtDbgBreak(); \ } while (0)

如果断言表达式的计算结果为 FALSE,则会调用 _CrtDbgReport 以报告断言失败(默认情况下使用消息对话框)。 如果在消息对话框中选择“重试”,则 _CrtDbgReport 会返回 1,_CrtDbgBreak 会通过 DebugBreak 调用调试器。

如果需要临时禁用所有断言,请使用 _CtrSetReportMode。

检查堆损坏

下面的示例使用 _CrtCheckMemory 检查堆是否损坏:

_ASSERTE(_CrtCheckMemory()); 检查指针有效性

下面的示例使用 _CrtIsValidPointer 验证给定内存范围对于读取或写入是否有效。

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

下面的示例使用 _CrtIsValidHeapPointer 验证指针是否指向本地堆中的内存(由 C 运行时库的此实例创建和管理的堆 — DLL 可以拥有自己的库实例,因此在应用程序堆之外拥有自己的堆)。 此断言不仅捕获 null 或超出界限地址,还捕获指向静态变量、堆栈变量和任何其他非本地内存的指针。

_ASSERTE(_CrtIsValidHeapPointer( myData ); 检查内存块

下面的示例使用 _CrtIsMemoryBlock 验证内存块是否处于本地堆中并且具有有效的块类型。

_ASSERTE(_CrtIsMemoryBlock (myData, size, &requestNumber, &filename, &linenumber));

在本主题中

MFC 断言

MFC 定义 ASSERT 宏以用于断言检查。 它还定义 MFC ASSERT_VALID 和 CObject::AssertValid 方法以用于检查 CObject 派生对象的内部状态。

如果 MFC ASSERT 宏的参数的计算结果为零或 false,则该宏会停止程序执行并向用户发出警报;否则,执行会继续。

当断言失败时,一个消息对话框会显示源文件的名称和断言的行号。 如果在该对话框中选择“重试”,则调用 AfxDebugBreak 会导致执行中断到调试器。 此时,可以检查调用堆栈,并使用其他调试器工具来确定断言失败的原因。 如果启用了实时调试并且调试器尚未运行,则该对话框可以启动调试器。

下面的示例演示如何使用 ASSERT 检查函数的返回值:

int x = SomeFunc(y); ASSERT(x >= 0); // Assertion fails if x is negative

可以将 ASSERT 与 IsKindOf 函数一起使用,以提供函数参数的类型检查:

ASSERT( pObject1->IsKindOf( RUNTIME_CLASS( CPerson ) ) );

在发布版本中,ASSERT 宏不生成任何代码。 如果需要在发布版本中计算表达式,请使用 VERIFY 宏而不是 ASSERT。

MFC ASSERT_VALID 和 CObject::AssertValid

CObject::AssertValid 方法提供对象内部状态的运行时检查。 尽管从 CObject 派生类时不需要替代 AssertValid,不过可以通过执行此操作使类更可靠。 AssertValid 应对对象的所有成员变量执行断言,以验证它们是否包含有效值。 例如,它应检查指针成员变量是否不为 NULL。

下面的示例演示如何声明 AssertValid 函数:

class CPerson : public CObject { protected: CString m_strName; float m_salary; public: #ifdef _DEBUG // Override virtual void AssertValid() const; #endif // ... };

替代 AssertValid 时,请先调用 AssertValid 的基类版本,然后再执行自己的检查。 随后使用 ASSERT 宏检查对派生类唯一的成员,如下所示:

#ifdef _DEBUG void CPerson::AssertValid() const { // Call inherited AssertValid first. CObject::AssertValid(); // Check CPerson members... // Must have a name. ASSERT( !m_strName.IsEmpty()); // Must have an income. ASSERT( m_salary > 0 ); } #endif

如果有任何成员变量存储对象,则可以使用 ASSERT_VALID 宏测试其内部有效性(如果它们的类会替代 AssertValid)。

例如,考虑一个类 CMyData,它将 CObList 存储在它的一个成员变量中。 CObList 变量 m_DataList 会存储 CPerson 对象的集合。 CMyData 的缩写声明如下所示:

class CMyData : public CObject { // Constructor and other members ... protected: CObList* m_pDataList; // Other declarations ... public: #ifdef _DEBUG // Override: virtual void AssertValid( ) const; #endif // And so on ... };

CMyData 中的 AssertValid 替代如下所示:

#ifdef _DEBUG void CMyData::AssertValid( ) const { // Call inherited AssertValid. CObject::AssertValid( ); // Check validity of CMyData members. ASSERT_VALID( m_pDataList ); // ... } #endif

CMyData 使用 AssertValid 机制测试其数据成员中存储的对象的有效性。 CMyData 的替代 AssertValid 会为其自己的 m_pDataList 成员变量调用 ASSERT_VALID。

有效性测试不会在此级别上停止,因为类 CObList 也会替代 AssertValid。 此替代会对列表的内部状态执行附加有效性测试。 因此,CMyData 对象上的有效性测试会对已存储 CObList 列表对象的内部状态形成附加有效性测试。

通过更多工作,还可以为存储在列表中的 CPerson 对象添加有效性测试。 可以从 CObList 派生类 CPersonList,并替代 AssertValid。 在替代中,会调用 CObject::AssertValid,然后循环访问列表,对列表中存储的每个 CPerson 对象调用 AssertValid。 本主题开头部分演示的 CPerson 类已替代 AssertValid。

在为调试进行生成时,这是一种功能强大的机制。 随后为发布进行生成时,该机制会自动关闭。

AssertValid 的限制

触发的断言指示对象肯定有错误,执行将停止。 但是,没有断言仅指示未发现问题,但不保证对象正确。

在本主题中

使用断言 捕获逻辑错误

可以根据程序的逻辑对必须为 true 的条件设置断言。 除非发生逻辑错误,否则断言不起作用。

例如,假设要模拟容器中的气体分子,而变量 numMols 表示分子的总数。 此数字不能小于零,因此可以包含如下所示的 MFC 断言语句:

ASSERT(numMols >= 0);

或者,可以包含如下所示的 CRT 断言:

_ASSERT(numMols >= 0);

如果程序运行正常,则这些语句不会执行任何操作。 但是,如果逻辑错误导致 numMols 小于零,则断言会停止程序执行,并显示断言失败对话框。

在本主题中

检查结果

对于测试无法通过快速的目视检查获得明显结果的操作,断言非常有用。

例如,考虑以下代码,它基于 mols 所指向的链接列表的内容来更新变量 iMols:

/* This code assumes that type has overloaded the != operator with const char * It also assumes that H2O is somewhere in that linked list. Otherwise we'll get an access violation... */ while (mols->type != "H2O") { iMols += mols->num; mols = mols->next; } ASSERT(iMols


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3