10.5 断言

在软件的开发过程中,通过条件编译引入printf调用等调试代码的做法是很常见的,但一般不会在发行版本中保留这些信息。然而经常会出现这样的情况,程序运行中出现的问题与不正确的假设有关而并非代码的错误。这些不正确的假设往往是被主观认为不会发生的事件。例如,人们在编写函数时会认为它的输入参数应该位于一个确定的范围内,但万一给它传递了不正确的数据,就可能造成整个系统运行不正常。

系统的内部逻辑需要被确认没有错误。针对这种情况,X/Open提供了assert宏,它的作用是测试某个假设是否成立,如果不成立就停止程序的运行。

10.5 断言 - 图1

assert宏对表达式进行求值,如果结果非零,它就往标准错误写一些诊断信息,然后调用abort函数结束程序的运行。

头文件assert.h定义的宏受NDEBUG的影响。如果程序在处理这个头文件时已经定义了NDEBUG,就不定义assert宏。这意味着,你可以在编译期间使用-DNDEBUG关闭断言功能或把下面这条语句:

10.5 断言 - 图2

加到每个源文件中,但这条语句必须放在#include <assert.h>语句之前。

assert宏的这种用法带来一个问题。如果在测试阶段使用assert,但在发行版本中将其关闭,那你的发行版本代码在安全检测方面就比你对它进行测试时要差一些。但在产品代码中保留assert通常是不可取的——难道你愿意用户在使用你的软件时在屏幕上显示一条不友好的assert failed错误提示,然后就退出程序吗?针对这个问题的比较好的解决方法是,编写自己的错误中断陷阱例程,在该例程中进行断言,但不需要在产品代码中完全禁用该功能。

你还必须注意不要让assert表达式带上副作用。例如,如果使用了带有副作用的函数调用,这个副作用在删除了断言功能的产品代码中就不会再发生了。

实 验 assert

下面这个程序assert.c定义了一个函数,它的参数必须是一个正数。它用断言功能来保护自己不受非法参数的影响。

该程序首先包括头文件assert.h,然后定义一个平方根函数,该函数检查自己的参数是否为正数,最后是main函数。如下所示:

10.5 断言 - 图3

现在,运行这个程序时,如果给my_sqrt函数传递了一个非法值,你就会看到一个断言冲突错误。错误信息的格式将随系统的不同而不同。

10.5 断言 - 图4

实验解析

当我们试图用一个负数来调用函数my_sqrt时,断言失败了。assert宏给出了发生断言冲突的文件名和行号,还给出了失败的条件。程序被一个abort中断陷阱终止了运行,这就是assert调用abort的结果。

如果用-DNDEBUG选项重新编译这个程序,断言功能将被排除在编译结果之外。当在my_sqrt函数中调用sqrt函数时,得到的将是一个NaN值(不是一个数字),表明一个无效结果,如下所示:

10.5 断言 - 图5

一些较旧的数学库版本在发生算术错误时将产生一个异常,程序将终止并返回一个类似Floating point exception的消息,而不是返回一个NaN。