debugging 在C/C++中Assert(false)的更好替代方法

hrirmatl  于 2022-11-14  发布在  C/C++
关注(0)|答案(9)|浏览(215)

现在我写

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进行编译时,最后一个Assert就会消失,编译器警告说,执行结束了函数,没有返回语句。
如果已经到达了一个被认为是无法到达的代码部分,有什么更好的替代方法来完成程序呢?

  • 被编译器识别并且不产生警告(如上面的警告或其他警告)
  • 甚至可能允许自定义错误消息。

我明确地对解决方案感兴趣,不管它是现代C++还是90年代的C。

kuhbmx9i

kuhbmx9i1#

替换你的assert(false)正是“不可达”内置函数的作用。
它们在语义上等同于assert(false)的用法,事实上,VS 's的拼写非常相似。
GCC/叮当声/英特尔:

__builtin_unreachable()

MSVS:

__assume(false)

无论NDEBUG(与assert不同)或优化级别如何,这些都有效。
你的编译器,特别是上面的内置编译器,也可能是assert(false)编译器,会点头表示你“承诺”函数的某一部分永远不会被执行。它可以利用这一点对特定的代码路径进行优化,并且它会关闭关于缺少返回的警告,因为你已经“承诺”这是故意的。
这样做的代价是语句本身有未定义的行为(很像函数已经结束了)。在某些情况下,你可能会考虑抛出一个异常(或者返回一些“错误代码”值),或者调用std::abort()(在C中)来终止程序。
有一个建议(P0627R0),将其作为标准属性添加到C
中。
the GCC docs on Builtins开始:
如果控制流到达__builtin_unreachable点,则程序未定义。这在编译器无法推断代码不可达的情况下很有用。[..]

wf82jlnq

wf82jlnq2#

作为一种完全可移植的解决方案,请考虑以下内容:

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

当然,消息部分是可选的。

crcmnpdw

crcmnpdw3#

我喜欢用

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

...也可以带条件使用,如

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

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

pxyaymoc

pxyaymoc4#

我使用了一个自定义Assert,当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()应该消除警告,同时帮助优化,但在调试模式下,最好有一个assert或abort();,这样您就可以获得可靠的死机。(__builtin_unreachable()在达到时只会给您未定义的行为)。

ylamdve6

ylamdve65#

看起来std::unreachable()已经进入C++23:
https://en.cppreference.com/w/cpp/utility/unreachable

cczfrluj

cczfrluj6#

我推荐C++ Core Gudelines的Expects和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);
}
dsf9zpds

dsf9zpds7#

assert是针对那些实际上不可能在执行过程中发生的情况。在调试中指出“嘿,原来你认为不可能的事情实际上并不是不可能的”是很有用的。看起来你 * 应该 * 在给定的例子中做的是表达函数的失败。可能是通过返回-1,因为这不是一个有效的索引。在某些情况下,设置errno来澄清错误的确切性质可能是有用的。然后,利用这个信息,调用函数可以决定如何处理这样的错误。
根据此错误对应用程序其余部分的严重程度,您可以尝试从中恢复,或者只记录此错误并调用exit来解决它。

zpjtge22

zpjtge228#

我相信你得到这些错误的原因是因为Assert通常被用来调试你自己的代码。当这些函数在发行版中运行时,应该使用异常来代替std::abort()的退出来指示程序异常终止。
如果您仍然想使用Assert,PSkocik提供了一个关于定义自定义Assert的答案,这里还有一个链接,有人建议使用自定义Assert,以及如何在cmake here中启用它们。

8ftvxx2r

8ftvxx2r9#

有时在样式指南中可以找到的一条规则是
“永远不要从函式中间传回”
所有函数都应该在函数的末尾有一个return。
遵循此规则,您的程式码看起来会像:

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!
}

相关问题