断言
断言语句指定希望为 true 在您的程序的点的行为。如果该条件未得到满足,则断言失败,中断程序的执行,因此,断言失败"对话框 显示。
Visual C++ 支持基于下列构造的断言语句:
可以使用断言捕捉逻辑错误,检查操作的结果和测试应处理的错误状态。
主题内容
断言的工作原理
断言调试和发布版本
使用断言的副作用
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调用调试器。
检查堆损坏
下面的示例使用 _CrtCheckMemory 检查堆是否损坏:
_ASSERTE(_CrtCheckMemory());
检查指针有效性
下面的示例使用 _CrtIsValidPointer 验证给定的内存范围对于读或写是否有效。
_ASSERTE(_CrtIsValidPointer( address, size, TRUE );
下面的示例使用 _CrtIsValidHeapPointer 来验证指针指向本地堆(由 C 运行库的这个实例创建和管理的堆;DLL 可以有它自己的库实例,因而也可以有它自己的、位于应用程序堆之外的堆)中的内存。该断言不仅捕捉空地址或超出边界的地址,还捕捉指向静态变量、堆栈变量和其他任何非本地内存的指针。
_ASSERTE(_CrtIsValidPointer( myData );
检查内存块
下面的示例使用 _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。
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 的限制
触发的断言指示对象一定有错误,并且执行将停止。但是,缺少断言仅指示未找到任何问题,但是,对象不能保证良好。
主题内容
使用断言
捕捉逻辑错误
可以在根据程序逻辑必须为真的条件上设置断言。除非发生逻辑错误,否则断言无任何影响。
例如,假定正在模拟容器中的气体分子,而变量 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<=numMols); // MFC version
_ASSERT(iMols<=numMols); // CRT version
由 iMols 计数的分子数必须始终小于或等于分子总数 numMols。对循环的直观检查并不说明这一定为真,因此在循环后使用一条断言语句来测试该条件。
主题内容
查找未处理错误
可以使用断言在代码中应已处理了所有错误的点处测试错误条件。在下面的示例中,一个图形例程将返回错误代码,或返回零表示成功。
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 返回的所有错误代码都未在最终发布代码中予以处理。
主题内容