很可笑,我很少用断言,对断言的不了解程度与初学者无异。今天翻看了《深入理解C++11》,新标准中对断言做了改进,至少说明断言还是很有用的。结合《代码大全》、《C++应用程序性能优化》把自己对断言的理解写下来以方便自己日后用到时查阅。
一、为什么要用断言
首先要搞清楚为什么要用断言,不能看别人代码中有,就追赶时髦地用一用!从效果上来说assert断言能用if语句替换,那么为什么不用if语句把断言替换呢?一般而言,if语句是处理逻辑上的可能会发生的错误,断言则用来处理不应该发生的状况。
什么是不应该发的的状况呢?这要区分数据的来源:1、数据来源于系统内部(子程序、子模块间的调用)2、数据来源于系统外部(外部设备如键盘的输入、串口数据的读取、网络数据的读取)。对内部来源的数据,我们没法去通过常规的测试手段去验证,此时断言就用上了。
当然你如果硬是要用if语句也没人说你不对,但大量的if语句出现在源码中时,会造成代码臃肿,降低了可读性,另外会产生不紧凑代码,影响效率。
程序开发初期,码农们忽视的是程序间调用参数的合法性,对这些参数可使用断言来防止意外,随着程序进入release版时,可以定义NDEBUG来让断言失效。以下是NDEGBU对assert的处理代码。
#ifdef NDEBUG
#define assert(expr) (static_cast<void> (0))
#else
......
#endif
二、如何使用断言
assert是一个宏,C语言原型定义在assert.h中,C++语言原型定义在cassert中,形式为:void assert(int expression);参数为表达式,如果为0则向stderr打印一条出错信息,再调用 abort来终止程序。如果为真,则继续执行其后的语句。
以下以伪代码方式讲解assert使用方式及注意事项:
// 从堆中动态申请内存
char * newBuff(int nSize/*申请内存大小*/)
{
assert(nSize> 0);
assert(nSize <= MAX_BUFFER_SIZE);
return (new char[nSize] );
}
int main()
{
char *pCh = newBuff(0);
......
delete pCh;
pCh = NULL;
......
}
说明:assert在子函数开始处对参数进行验证,验证条件分开,这样出现问题可以准确定位错误,如写成:assert(nSize>0 && nSzie<=MAX_BUFFER_SIZE);这样就不好了。
assert中不能有改变变量状态的操作,如:assert(iCount++ >= 0);
需要注意的是:assert是宏,编译时会在使用处进行代码展开,如果程序中大量且频繁使用,会造成代码臃肿,影响性能。
三、C++11中关于断言的表述
上面例子中的断言只能在程序运行时起作用。即是动态断言。有时,程序员更希望在编译期间就能使用断言,这类断言称为静态断言,C++11提供了支持。
3.1、动态断言示例
enum FeatureSupports{
C99 = 0x0001,
ExtInt = 0x0004,
SAssert=0x0008,
NoExcept= 0x0010,
};
struct Compiler{
const char * name;
int spp; // 使用FeatureSuppport枚举
};
int main()
{
assert((SMAX-1) == (C99 | ExtInt | SAssert | NoExcept ));
Compiler a = {"abc", (C99 | SAssert)};
......
if( a.spp & C99)
......
}
以上是常见的C代码,功能是按位存储属性,枚举类中列出了编译器支持的各种属性,Compiler类成员变量spp,则是用来对属性的选择而设置的。主程序中就使用assert对所有的枚举量进行校验。采用的方法很巧妙,利用了位或的特点来验证,只要有一个类型写错或漏写,通过断言都能发现!遗憾的是上述程序必须运行后才能发现问题!
C++关于模板的实现中也可以举出相应的例子:
template <class T, class U>
int bit_copy(T& a, U& b){
assert(sizeof(a) == sizeof(b));
memcpy(&a, &b, sizeof(b));
}
int main()
{
int a = 0x1234;
double b= 0.11;
bit_copy(a, b);
return 1;
}
3.2、静态断言示例
结合assert_static宏定义和“除0”出错,C++98实现静态断言。
#define assert_static(e) \
do{ \
enum{ assert_static__ = 1/(e)}; \
}while(0)
template <class T, class U>
int bit_copy(T& a, U& b){
assert_static(sizeof(a) == sizeof(b));
memcpy(&a, &b, sizeof(b));
}
int main()
{
int a = 0x1234;
double b= 0.11;
bit_copy(a, b);
return 1;
}
template <class T, class U>
int bit_copy(T& a, U& b){
static_assert(sizeof(a) == sizeof(b), "the parameters of bit_copy should have same width");
memcpy(&a, &b, sizeof(b));
}
int main()
{
int a = 0x1234;
double b= 0.11;
bit_copy(a, b);
return 1;
}
注意:C++98中定义的是assert_static,C++11中则为static_assert,正好对调!此时C++11编译要使用-std=c++11开关项。四、关于断言的使用原则
最后分享一下《代码大全》中对断言使用的建议:
1、用错误处理代码处理预期会发生的状况,用断言来处理绝不应该发生的状况;
2、避免把需要执行的代码放到断言中;
3、用断言来注解并验证前条件和后条件;
4、对于高健壮性的代码,应该先使用断言再处理错误 ;