在C++中有时会用到类似于头文件保护的技术,以便有选择的执行调试代码。基本思想是,程序可以包含一些用于调试的代码,但是这些代码只在开发程序的时候使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码。这种方法用到了两种预处理功能:assert和NDEBUG。
assert预处理宏
assert是一种预处理宏。所谓预处理宏其实就是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:
assert(expr);
首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行。如果表达式为真,assert什么也不做。
assert宏常用于检查“不能发生”的条件。例如,一个对输入文本进行操作的程序可能要求所有给定单词的长度都大于某个阀值。此时,程序可以包含一条如下所示的语句:
assert(word.size()>threshold)
NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
我们可以使用一个#define语句定义NDEBUG,从而关闭调试状态。
定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。因此,assert应该仅用于验证那些确实不可能发生的事。我们可以把assert当成调试程序的一种辅助手段,但是不能用它代替真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查。
除了使用assert外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef和#endif之间的代码;如果定义了NDEBUG,这些代码将被忽略。
void pring(const int ia[], size_t size)
{
#ifndef NDEBUG
// __func__ 编译器定义的一个局部静态变量,用于存放函数的名字
cerr << __func__ << ":array size is " << size << endl;
#endlf
}
C++编译器除了定义了之外,预处理器还定义了另外4个对于调试很有用的名字:
名字 | 作用 |
---|---|
__func__ | 当前调试的函数名字 |
__FILE__ | 存放文件名的字符串字面值 |
__LINE__ | 存放当前行号的整型字面值 |
__TIME__ | 存放文件编译时间的字符串字面值 |
__DATE__ | 存放文件编译日期的字符串字面值 |
如下面的例子:
if(word.size() < threshold)
cerr << "Error: " << __FILE__
<< ": in function " << __func__
<< " at line " << __LINE__ << endl
<< " Compiled on " << __DATA__
<< " at " << __TIME__ << endl;