2.5 静态断言
类别:库作者
2.5.1 断言:运行时与预处理时
断言(assertion)是一种编程中常用的手段。在通常情况下,断言就是将一个返回值总是需要为真的判别式放在语句中,用于排除在设计的逻辑上不应该产生的情况。比如一个函数总需要输入在一定的范围内的参数,那么程序员就可以对该参数使用断言,以迫使在该参数发生异常的时候程序退出,从而避免程序陷入逻辑的混乱。
从一些意义上讲,断言并不是正常程序所必需的,不过对于程序调试来说,通常断言能够帮助程序开发者快速定位那些违反了某些前提条件的程序错误。在C++中,标准在<cassert>或<assert.h>头文件中为程序员提供了assert宏,用于在运行时进行断言。我们可以看看下面这个例子,如代码清单2-6所示。
代码清单2-6
include <cassert>
using namespace std;
//一个简单的堆内存数组分配函数
char*ArrayAlloc(int n){
assert(n>0);//断言,n必须大于0
return new char[n];
}
int main(){
char*a=ArrayAlloc(0);
}
//编译选项:g++2-5-1.cpp
在代码清单2-6中,我们定义了一个ArrayAlloc函数,该函数的唯一功能就是在堆上分配字节长度为n的数组并返回。为了避免意外发生,函数ArrayAlloc对参数n进行了断言,要求其大于0。而main函数中对ArrayAlloc的使用却没有满足这个条件,那么在运行时,我们可以看到如下结果:
a.out:2-5-1.cpp:6:char*ArrayAlloc(int):Assertion`n>0'failed.
Aborted
在C++中,程序员也可以定义宏NDEBUG来禁用assert宏。这对发布程序来说还是必要的。因为程序用户对程序退出总是敏感的,而且部分的程序错误也未必会导致程序全部功能失效。那么通过定义NDEBUG宏发布程序就可以尽量避免程序退出的状况。而当程序有问题时,通过没有定义宏NDEBUG的版本,程序员则可以比较容易地找到出问题的位置。事实上,assert宏在<cassert>中的实现方式类似于下列形式:
ifdef NDEBUG
define assert(expr)(static_cast<void>(0))
else
…
endif
可以看到,一旦定义了NDBUG宏,assert宏将被展开为一条无意义的C语句(通常会被编译器优化掉)。
在2.4节中,我们还看到了#error这样的预处理指令,而事实上,通过预处理指令#if和#error的配合,也可以让程序员在预处理阶段进行断言。这样的用法也是极为常见的,比如GNU的cmathcalls.h头文件中(在我们实验机上,该文件位于/usr/include/bits/cmathcalls.h),我们会看到如下代码:
ifndef_COMPLEX_H
error"Never use<bits/cmathcalls.h>directly;include<complex.h>instead."
endif
如果程序员直接包含头文件<bits/cmathcalls.h>并进行编译,就会引发错误。#error指令会将后面的语句输出,从而提醒用户不要直接使用这个头文件,而应该包含头文件<complex.h>。这样一来,通过预处理时的断言,库发布者就可以避免一些头文件的引用问题。