C/C++ 中 assert(false) 的更好替代方案

Better alternatives to assert(false) in C/C++

目前,我写

assert(false);

在我的代码不应该到达的地方。一个非常 C 风格的例子是:

int findzero( int length, int * array ) {
  for( int i = 0; i < length; i++ )
    if( array[i] == 0 )
      return i;
  assert(false);
}

我的编译器识别出程序在达到 assert(false) 时结束。但是,每当我出于性能原因使用 -DNDEBUG 进行编译时,最后一个断言就会消失,并且编译器会警告执行完成函数时没有 return 语句。

如果已经达到了假定无法到达的代码部分,有什么更好的方法可以结束程序?解决方案应该

无论是现代 C++ 还是 90 年代的 C,我都对解决方案很感兴趣。

替换您的 assert(false) 正是 "unreachable" 内置函数的用途。

它们在语义上等同于您对 assert(false) 的使用。其实VS的拼法很像

GCC/Clang/Intel:

__builtin_unreachable()

MSVS:

 __assume(false)

无论 NDEBUG(不同于 assert)或优化级别如何,这些都有效。

你的编译器,特别是上面的内置函数,但也可能是你的 assert(false),点头表示你有希望那部分永远达不到功能。它可以使用它在某些代码路径上执行一些优化,并且它会消除关于丢失 returns 的警告,因为你已经 承诺 这是故意的。

权衡是语句本身具有未定义的行为(很像前进并从函数的末尾流出已经是)。在某些情况下,您可能希望考虑抛出异常(或返回一些 "error code" 值),或者如果您只想终止程序则调用 std::abort()(在 C++ 中)。


有人提议 (P0627R0),将其作为标准属性添加到 C++。


来自the GCC docs on Builtins

If control flow reaches the point of the __builtin_unreachable, the program is undefined. It is useful in situations where the compiler cannot deduce the unreachability of the code. [..]

风格指南中有时会出现的一条规则是

"Never return from the middle of a function"
All functions should have a single return, at the end of the function.

遵循此规则,您的代码将如下所示:

int findzero( int length, int * array ) {
  int i;
  for( i = 0; i < length; i++ )
  {
    if( array[i] == 0 )
      break;             // Break the loop now that i is the correct value
  }

  assert(i < length);    // Assert that a valid index was found.
  return i;              // Return the value found, or "length" if not found!
}

我使用自定义断言,当 NDEBUG 打开时,它变成 __builtin_unreachable()*(char*)0=0(我还使用枚举变量而不是宏,以便我可以轻松设置 NDEBUG每个范围)。

在伪代码中,它类似于:

#define my_assert(X) do{ \ 
       if(!(X)){ \
           if (my_ndebug) MY_UNREACHABLE();  \
           else my_assert_fail(__FILE__,__LINE__,#X); \
       } \
     }while(0)

__builtin_unreachable() 应该消除警告并同时帮助优化,但在调试模式下,最好在那里有一个断言或 abort(); 这样你就可以获得可靠的恐慌。 (__builtin_unreachable() 到达时只会给你未定义的行为)。

assert 适用于在执行过程中实际上应该不可能发生的场景。在调试中指出 "Hey, turns out what you thought to be impossible is, in fact, not impossible." 很有用 看起来你 应该 在给定的例子中做的是表达函数的失败,也许通过返回 -1 作为那不是一个有效的索引。在某些情况下,设置 errno 可能有助于阐明错误的确切性质。然后,根据这些信息,调用函数可以决定如何处理此类错误。

根据此错误对应用程序其余部分的严重程度,您可能会尝试从中恢复,或者您可能只是记录错误并调用 exit 使其摆脱困境。

我推荐C++ Core Gudelines's Expects and Ensures。它们可以配置为中止(默认)、抛出或在违规时什么都不做。

要在无法到达的分支上抑制编译器警告,您还可以使用 GSL_ASSUME

#include <gsl/gsl>

int findzero( int length, int * array ) {
  Expects(length >= 0);
  Expects(array != nullptr);

  for( int i = 0; i < length; i++ )
    if( array[i] == 0 )
      return i;

  Expects(false);
  // or
  // GSL_ASSUME(false);
}

我相信您收到错误的原因是因为断言通常用于调试您自己的代码。当这些函数在 运行 发布时,应该使用异常来代替 std::abort() 的退出来指示程序异常终止。

如果您仍想使用断言,PSkocik 提供了关于定义自定义断言的答案,link 这里有人建议使用自定义断言以及如何在 cmake 中启用它们here 还有。

作为一个完全可移植的解决方案,考虑一下:

[[ noreturn ]] void unreachable(std::string_view msg = "<No Message>") {
    std::cerr << "Unreachable code reached. Message: " << msg << std::endl;
    std::abort();
}

消息部分当然是可选的。

我喜欢用

assert(!"This should never happen.");

...也可以与条件一起使用,如

assert(!vector.empty() || !"Cannot take element from empty container." );

这样做的好处是,如果断言不成立,字符串会显示在错误消息中。