断言

断言语句指定希望为 true 在您的程序的点的行为。 如果该条件未得到满足,则断言失败,中断程序的执行,因此,断言失败"对话框 显示。

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

  • MFC 程序的 MFC 断言。

  • 使用 ATL 的程序的 ATLASSERT

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

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

可以使用断言捕捉逻辑错误,检查操作的结果和测试应处理的错误状态。

主题内容

断言的工作原理

断言调试和发布版本

使用断言的副作用

CRT 断言

MFC 断言

  • MFC ASSERT_VALID 和 CObject::AssertValid

  • AssertValid 的限制

使用断言

  • 捕捉逻辑错误

  • 检查结果

  • 查找未处理错误

断言的工作原理

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

在开发过程中,使用断言错误。 通常,对于每个假设使用一断言。 例如,因此,如果您认为,参数不为空,则使用断言测试这种假设。

主题内容

断言调试和发布版本

只有 + 当 _DEBUG 定义,断言语句生成。 否则,编译器将断言作为空语句处理。 因此,断言语句不会开销或性能在最终发布程序花费了,并允许您避免使用 #ifdef 指令。

使用断言的副作用

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

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

由于 ASSERT 表达式在程序的" release "版本中不计算表达式,nM 在调试版本和发布版本不同的值。 若要避免在 MFC 的此问题,可以使用 验证 宏而不是 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 断言的每个表达式,应用程序的“Debug”版本均包含一个字符串常数。 如果使用许多 _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调用调试器。

ww5t02fa.collapse_all(zh-cn,VS.110).gif检查堆损坏

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

_ASSERTE(_CrtCheckMemory());

ww5t02fa.collapse_all(zh-cn,VS.110).gif检查指针有效性

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

_ASSERTE(_CrtIsValidPointer( address, size, TRUE );

下面的示例使用 _CrtIsValidHeapPointer 来验证指针指向本地堆(由 C 运行库的这个实例创建和管理的堆;DLL 可以有它自己的库实例,因而也可以有它自己的、位于应用程序堆之外的堆)中的内存。 该断言不仅捕捉空地址或超出边界的地址,还捕捉指向静态变量、堆栈变量和其他任何非本地内存的指针。

_ASSERTE(_CrtIsValidPointer( myData );

ww5t02fa.collapse_all(zh-cn,VS.110).gif检查内存块

下面的示例使用 _CrtIsMemoryBlock 验证某内存块在本地堆中,并且有有效的块类型。

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

主题内容

MFC 断言

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

如果 MFC ASSERT 宏的参数的计算结果为零或错误,宏来暂停程序执行并警告用户;否则,继续执行。

当断言失败时,消息对话框显示断言的源文件的名称和行号。 如果在该对话框中选择“重试”,则对 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。

ww5t02fa.collapse_all(zh-cn,VS.110).gifMFC 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 类,该类在其成员变量之一中存储了一个 CObListCObList 变量 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

当为调试生成时,这是一种功能极强的机制。 当接着为发布生成时,该机制自动关闭。

ww5t02fa.collapse_all(zh-cn,VS.110).gifAssertValid 的限制

触发的断言指示对象一定有错误,并且执行将停止。 但是,缺少断言仅指示未找到任何问题,但是,对象不能保证良好。

主题内容

使用断言

ww5t02fa.collapse_all(zh-cn,VS.110).gif捕捉逻辑错误

可以在根据程序逻辑必须为真的条件上设置断言。 除非发生逻辑错误,否则断言无任何影响。

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

ASSERT(numMols >= 0);

或者您可以包含如下的 CRT 断言:

_ASSERT(numMols >= 0);

程序运行正确时这些语句无任何影响。 如果逻辑错误导致 numMols小于零,但是,断言将暂停程序执行并显示 “断言失败”对话框

主题内容

ww5t02fa.collapse_all(zh-cn,VS.110).gif检查结果

断言对于测试结果从快速目测检验不明显的操作有价值。

例如,研究一下以下代码,这段代码根据 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<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version

由 iMols 计数的分子数必须始终小于或等于分子总数 numMols。 对循环的直观检查并不说明这一定为真,因此在循环后使用一条断言语句来测试该条件。

主题内容

ww5t02fa.collapse_all(zh-cn,VS.110).gif查找未处理错误

可以使用断言在代码中应已处理了所有错误的点处测试错误条件。 在下面的示例中,一个图形例程将返回错误代码,或返回零表示成功。

myErr = myGraphRoutine(a, b);

/* Code to handle errors and
   reset myErr if successful */

ASSERT(!myErr); -- MFC version
_ASSERT(!myErr); -- CRT version

如果错误处理代码工作正确,在到达断言之前应已处理错误并将 myErr 重置为零。 如果 myErr 具有其他值,则断言失败,程序暂停,并且显示 “断言失败”对话框

但是断言语句不是错误处理代码的替代物。 下面的示例显示会在最终发布代码中导致问题的断言语句:

myErr = myGraphRoutine(a, b);

/* No Code to handle errors */

ASSERT(!myErr); // Don't do this!
_ASSERT(!myErr); // Don't do this, either!

这段代码依赖断言语句来处理错误条件。 结果,myGraphRoutine 返回的所有错误代码都未在最终发布代码中予以处理。

主题内容

请参见

参考

托管代码中的断言

概念

调试器安全

其他资源

调试本机代码