【C++】C++中assert和ENDEGU预处理语句
assert
断言语句是C++中的一种预处理宏语句,它能在程序运行时根据否定条件中断程序。
C++中的assert()函数可以实现断言功能,在使用assert函数之前应该先引入<cassert>头文件。
函数:
void assert (int expression);
如果参数表达式不为0(也就是true),那么什么都不会发生。参数表达式为0(也就是false),那么将会有一条标准的错误消息被打印,随后调用abort中断运行程序。
打印的错误消息内容依据不同的实现库会有不同的消息内容。但消息内容至少应该包括:断言失败的表达式,文件的名称,断言失败处在文件中的行数。
比如:
#include <array> /*array*/ #include <assert> /*assert()*/ using namespace std; int getArrayValue(const std::array<int, 10> &array, int index) { // 断言index的范围是[0,9] assert(index >= 0 && index <= 9); // 这个是test.cpp的第7行 return array[index]; } int main(int argc,char **argv){ array<int,10> array = {1,2,3,4,5,6,7,8,9,10}; getArrayValue(array,-3);//调用getArrayValue函数,传入一个错误的index值 return 0; }
会得到如下的报错信息:
test.cpp:7: int getArrayValue(std::array<int, 10ul>, int): Assertion `index >=0 && index <=9' failed.
如何给断言语句添加额外的描述信息
有时断言语句的描述信息不够清晰,比如考虑如下这种情况:
assert(found);
如果这个断言被触发,那么会得到类似于如下的错误信息:
file_name:line_number : function_name: Assertion 'found' failed.
这种情况,如果只看断言输出是得不到详细的信息,必需要去查看代码才能确定是什么原因导致了错误。
那么有没有什么方式可以为断言添加一些额外的描述信息呢?答案是有的。我们可以利用C++中的&&符,添加一个字符串字面值的描述信息。
assert(found && "Car could not found in database");
由于字符串字面值总是为true,所以整个表达式的真假性还是取决于found变量。这样的话,既添加了额外的描述信息,又不改变原来的含义。
如果断言触发,会得到如下的结果:
file_name:line_number : function_name: Assertion 'found && "Car could not found in database"' failed.
从断言输出的错误信息中,可以看到详细的错误描述。
static_assert
在c++11标准中添加了一种静态断言static_assert,和assert不同的是:assert表达式的检查在运行时发生,static_assert的检查在编译时发生。
如果static_assert断言被触发的话,那么会在控制台打印错误信息,同时编译失败。若static_assert断言未被触发,那么编译顺利通过。
比如:
static_assert(sizeof(long) == 8, "long must be 8 bytes"); int main(){return 0;}
如果编译机器long类型不占8个字节的话,那么在编译的时候就会出现如下的错误信息:
static assertion failed: long must be 8 bytes
因为static_assert对条件式的检查发生在编译时,所以条件表达式一定要能在编译时被计算出来。在c++11中,static_assert的第二个参数是必需提供的,到c++17,第二个参数是可选的。
EDEBUG
在上面介绍了assert断言语句,其实assert的行为依赖一个名为NDEBUG的预处理变量的状态,如果定义了ENDEBUG,则assert什么也不会发生。默认状态下没有定义NDEBUG,assert会执行运行时检查。
例如:
#define NDEBUG #include <assert> /*assert()*/ int getArrayValue(const std::array<int, 10> &array, int index) { // 断言index的范围是[0,9] assert(index >= 0 && index <= 9); // 这个是test.cpp的第7行 return array[index]; }
注意#define NDEBUG必须要写在#include <assert>语句之前。
定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。除了使用assert外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef和#endif之间的代码;如果定义了NDEBUG,这些代码将会被忽略掉:
void print(const int ia[],size_t size){ #ifndef NDEBUG cerr << __func__ << ": array size is" << size << endl; #endif //... }
上面这段代码中,我们使用变量__func__输出当前调试函数的名字。编译器为每个函数都定义了__func__,它是const char的静态数组,用于存放函数的名称。
C++编译器除了定义过__func__,预处理器还定义了一些调试非常有用的名字。
__FILE__ 存放文件名的字符串字面值
__LINE__ 存放当前行号的整形字面值
__TIME__ 存放文件编译时间的字符串字面值
__DATE__ 存放文件编译日期的字符串字面值
可以使用这些变量在错误消息中提供更多信息,例如:
if(word.size() < minLen) cerr << "Error : " << __FILE__ << " : In function " << __func__ << " at Line " << __LINE__ << endl << " Compiled on " << __DATE__ << " at time " << __TIME__ << endl << " Word read was \"" << word << "\" : Length too short" << endl;
如果提供一个小于minLen的变量,那么会得到类似如下的错误信息:
Error : test.cpp : In function main at line 27 Compiled on Jul 11 2019 at 20:00:00 Word read was "foo" : Length too short