clpusplus /assert cppreference /assert
Part1 Assert
assert旨在捕获程序员的编程错误(例如传入非法参数),而不是用户错误或运行时错误(如返回错误值或在 C++ 中抛出异常),因为它通常在程序退出调试阶段后被禁用。在程序运行时,一旦遇到断言不成立,程序即会报错终止,以方便程序调试。
assert是一个function-like marco,那么我们在程序中中调用assert(condition),编译器就会去看assert的宏定义,这个宏定义是在编译时处理好的。即:
- 如果当前没有定义
NDEBUG ,则执行# define assert(condition) /*implementation defined*/ ;
- if condition 计算结果为 0,说明断言失败,程序崩溃:assert在standard error output中写入一条诊断信息,并调用std::abort终止程序执行。
- else,继续执行
- 如果定义了
NDEBUG ,则执行# define assert(condition) ((void)0) ,即assert不做任何事情。
#ifdef NDEBUG
# define assert(condition) ((void)0)
#else
# define assert(condition) \
#endif
以下是三点解释:
-
宏assert的定义依赖于另一个宏NDEBUG 。assert使用太多会影响系统性能,因此需要通过宏,把assert编译成debug版本和release版本。尽管NDEBUG 不是标准库中的宏,但是基本上编译器都在实现<cassert>的时候对其做了定义,因此编程的时候也要先#include<cassert> 再使用。如果定义了NDEBUG ,说明当前是release版本,不执行assert。 -
诊断信息的细节取决于特定的库实现,但至少应包括:断言失败的表达式、源文件的名称__FILE__ (预定义宏,输出的是整个路径,路径中包含了文件名)和抛出异常的表达式在源文件中的行号__LINE__ (预定义宏),在C++11后,一般还包括表达式所属函数的名称__func__ ((预定义变量,返回以 null 结尾的 const char 字符数组,该数组包含函数名称)。这些宏被预定义在<cstdio>中。 错误信息格式举例: a.out: main.cpp:13: int main(): \
Assertion `((void)"There are five lights", 2+2==5)' failed.
-
assert(condition)说到底还是一个function-like marco,只是在编译时进行简单的替换, 因此condition中任何不受括号保护的逗号都被解释为宏参数分隔符,常见于模板参数列表和列表初始化中: assert(std::is_same_v<int, int>);
assert((std::is_same_v<int, int>));
static_assert(std::is_same_v<int, int>);
Part2 宏的编写技巧
——do-while合并多条语句 【错误示例1】
#define M() a(); b()
if (cond)
M();
else
c();
if (cond)
a(); b();
else
c();
【错误示例2】
#define M() { a(); b(); }
if (cond)
{ a(); b(); };
else
c();
【正确写法】
#define M() do { a(); b(); } while(0)
if (cond)
do { a(); b(); } while(0);
else
c();
Part3 Static Assert(Since C++ 11)
cppreference /Declarations cppreference /static_assert declaration
关键字static往往代表将运行时处理的内容提前至编译时处理,对于assert来说同样如此
语法格式:
static_assert ( bool-constexpr , message )
static_assert ( bool-constexpr )
第一个参数是布尔类型的常量表达式,即可以在编译时计算的表达式。事实上assert的condition并未明确规定布尔类型,但是C++17明确指出如果condition是布尔类型表达式,则必须为常量表达式。这是一件很好理解的事情,因为static_assert发生在编译期,如果其condition无法在编译期计算就相当麻烦。而C++17后,把这种bool-constexpr放在编译期去做,本质和constexpr关键字所做的没什么区别,都是缓解了运行期的压力。 第二个参数在C++ 17后变为可选参数。
-
static_assert的断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式,一旦表达式里出现变量,报错。 static_assert(sizeof(int) == 4, "64-bit code generation is not supported.");
static_assert(n > 0, "value must > 0");
-
如果常量表达式依赖于某些模板参数,则延迟到模板实例化时再进行演算,这就让检查模板参数也成为了可能。 template <typename T, typename U> int bit_copy(T& a, U& b)
{
static_assert(sizeof(b) == sizeof(a), "template parameter size no equal!");
};
-
由于static_assert是编译期断言,不生成目标代码,因此static_assert不会造成任何运行期性能损失。
|