目录
assert简介
在C/C++中, assert
是一种用于调试的宏,主要用于验证程序中的某些假设条件是否成立。如果条件不成立, assert
会中断程序的执行,并输出相关信息,帮助开发者定位问题 。assert
是动态断言,在程序运行期进行条件检查,C++11中引入了static_assert
静态断言用于编译期进行条件检查。下面我们围绕这两点进行展开。
C语言中,assert宏的原型定义在<assert.h>
中,而在C++中则定义在<cassert>
中。虽然二者头文件名称不同,但它们的功能都是相同的。但是<cassert>
提供了以下优势:
- 命名空间:
<cassert>
头文件中的assert
宏被定义在std
命名空间中,这可以避免命名冲突。 - 模板支持:
<cassert>
头文件中的assert
宏支持模板参数,这可以使得断言更灵活。 - C++ 特有的功能:
<cassert>
头文件提供了一些 C++ 特有的功能,例如支持std::string
类型的断言。
所以在c++中使用时最好包含<cassert>
而不是<assert.h>
。
assert
语法规范如下:
#include <cassert>
assert(expression);
expression
是一个bool
表达式,程序运行时若该表达式为真(1),则通过断言,继续往下执行;若表达式判定为假(0),则会由stderr
打印一条出错信息,并调用abort()
中断程序的执行。
#include <iostream>
#include <cassert>
static double balance = 200.0;
void withdraw(double amount) {
assert(amount > 0); //所取金额必须大于0
balance -= amount;
assert(balance >= 0); //余额必须大于等于0
}
int main() {
withdraw(202.5);
std::cout << "Balance:" << balance << std::endl;
return 0;
}
上述用例为用户取款的简易实现,在取款方法入口处对传入的参数进行检查,并且对取款之后的余额进行检查。在main
中我们调用该方法取超过目前余额的款,运行得到如下结果:
Assertion failed: balance >= 0, file another.cc, line 8
频繁地调用assert
会增加额外的性能开销,极大影响程序的性能。因此assert
也只用于debug版本中,在release版本中需要去掉以提高性能 ,且避免因断言失败导致用户端程序崩溃 。可以通过定义NDEBUG
宏来禁用assert
的调用。将上述用例做如下修改:
#include <iostream>
#define NDEBUG
#include <cassert>
再次编译运行得:
Balance:-2.5
注意:定义NDEBUG
宏应在包含头文件<cassert>
之前,否则在头文件中, #ifndef NDEBUG
宏已经被评估为假,断言信息已经被打开,那么再定义NDEBUG
就已经无效了。
(Tips:编译时加上 -DNDEBUG
参数也会实现同样的效果。 )
g++ -DNDEBUG another.cc
assert的部分应用场景及注意事项
函数入口处参数合法性校验:
在函数入口处使用 assert ,验证传入参数是否符合预期。这样可以在开发阶段快速捕获传递错误。
#include <stdio.h>
#include <assert.h>
int function(int parameter) {
assert(parameter >= 0); //确保输入非负
if (parameter == 0)
return 0;
return function(parameter - 1) + parameter;
}
int main() {
function(-2);
return 0;
}
--Output:
Assertion failed!
Program: D:\vsc\Cpp\a.exe
File: another.cc, Line 5
Expression: parameter >= 0
边界条件的校验:
在循环、递归等复杂操作中,确保变量保持在合法范围内。
#include <stdio.h>
#include <assert.h>
void printArr(int arr[], int length) {
for(int i = 0 ; i < length ; ++i) {
assert(i >= 0); assert(i < length);
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int myArr[5] = {1,2,3,4,5};
printArr(myArr, sizeof(myArr)/sizeof(int));
return 0;
}
逻辑验证:
验证程序运行时的逻辑假设,确保程序状态符合预期 。如上述取款用例:
#include <iostream>
#include <cassert>
int main() {
double balance = 100;
double amount = 95;
balance -= amount; //模拟非法扣款
assert(balance >= 0); //验证余额非负
return 0;
}
注意:
用assert
时一次只检验一个条件,不要同时检验多个条件。否则如果断言失败,无法直观的判断是哪个条件失败,从而使得assert
失去原本的意义。
#include <iostream>
#include <cassert>
void function(int argc, bool flag) {
//assert(argc > 0 && flag); //不好,不要这么用
assert(argc > 0); assert(flag); //好
}
int main() {
function(4,false);
return 0;
}
assert
的表达式中仅用于检查纯条件,不要修改环境中变量的状态。
int x = 5;
assert(x++ >= 0); //Error.
这么使用是错误的,当assert
被禁用时,表达式x的值不会被增加,从而导致影响下列的运行逻辑。
不要滥用assert
,assert
不能代替错误处理。
std::string *ptr_to_str = new (std::nothrow) std::string("hello world");
assert(ptr_to_str != nullptr);
当分配资源失败时,在调试环境下将会直接终止运行。但是如果在实际生产环境下,程序将会继续运行下去,导致不可预测的后果。
调试模式下,每次调用 assert
都会产生开销(条件检查和输出逻辑)。因此在高性能需求的代码中,应避免频繁调用assert
。
assert的优缺点
优点:
- 快速验证:方便在开发阶段捕捉程序逻辑问题。
- 轻量级:不会影响发布模式性能(
NDEBUG
定义后被移除)。 - 定位清晰:断言失败时直接指向问题代码行
缺点:
- 运行时终止:断言失败直接中断程序,不适合需要错误恢复的场景。
- 生产环境无效:发布模式下被移除,无法提供运行时验证。
- 局限于布尔表达式:不能提供复杂的错误上下文信息 。