C/C++ assert断言详解

目录

assert简介

assert的部分应用场景及注意事项

assert的优缺点



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的值不会被增加,从而导致影响下列的运行逻辑。

        不要滥用assertassert不能代替错误处理。

  std::string *ptr_to_str = new (std::nothrow) std::string("hello world");
  assert(ptr_to_str != nullptr);

当分配资源失败时,在调试环境下将会直接终止运行。但是如果在实际生产环境下,程序将会继续运行下去,导致不可预测的后果。

        调试模式下,每次调用 assert 都会产生开销(条件检查和输出逻辑)。因此在高性能需求的代码中,应避免频繁调用assert


assert的优缺点

优点: ​​​​​​​        

  • 快速验证:方便在开发阶段捕捉程序逻辑问题。
  • 轻量级:不会影响发布模式性能( NDEBUG 定义后被移除)。
  • 定位清晰:断言失败时直接指向问题代码行

缺点:

  • 运行时终止:断言失败直接中断程序,不适合需要错误恢复的场景。
  • 生产环境无效:发布模式下被移除,无法提供运行时验证。
  • 局限于布尔表达式:不能提供复杂的错误上下文信息 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值