assert(0)是什么意思?

39
我在考试中遇到了这样一个问题,至今仍不太确定如何回答它。我知道assertions是测试程序的方式,但我不太确定assert(0)检查的是什么。这是一个诡计吗?它总是会失败,但我不明白原因。它在检查什么?
请解释一下,谢谢。

4
多使用感叹号:assert(!!!"你曾希望不会发生的事情,现在已经发生了!"); - Hans Passant
只需注意使用奇数个感叹号。考虑特里·普拉切特的闲话“多个感叹号”,他摇了摇头,“是疯狂思维的明证。”而且,3个感叹号肯定是“多个”,唯一的解决方案就是1个。无论如何,我记得Andrei Alexandrescu和我都没有达成最佳方式的共识。Andrei支持assert(“Blah”&&condition),而我支持assert((“Blah”,condition))(我们两个都不喜欢感叹号)。现在我用&&,因为它更简单、更易懂。但这就是为什么我之后支持(())的原因... - Cheers and hth. - Alf
4个回答

33

C++标准将assert的定义推迟到C标准。

C99 §7.2/2:

assert宏将诊断测试放入程序中。它扩展为一个void表达式。当执行时,如果expression(必须具有标量类型)为false(即等于0),assert宏会以实现定义的格式将有关失败调用的特定信息(包括参数的文本、源文件的名称、源行号和封闭函数的名称-后者分别是预处理宏__FILE__和__LINE__和标识符__func__的值)写入标准错误文件。然后调用abort函数。


assert(0)中,0被解释为false,因此当启用断言检查时,此断言将始终失败或触发。

因此,它断言:

“执行永远不会到达此点。”

在实践中,使编译器停止关注执行是否到达给定点可能很困难。通常,编译器将首先抱怨函数可能到达末尾而不返回值。在那里添加一个assert(0)应该理想地解决了这个问题,但是编译器可能会抱怨assert,或者不认识它并尝试发出警告。

因此,一种可能的措施是在那一点上还抛出一个异常:

auto foo( int x )
    -> int
{
    if( x == 1 ) { return 42; }
    assert( 0 ); throw 0;       // Should never get here!
}

当然,这个双重打击可以被定义为一个更高级别的宏。关于异常类型,您可能希望将其保留为非std::exception,因为这不是一个普通的catch任何地方都可以捕获的异常。或者,如果您信任标准异常层次结构(我觉得这没有意义,但)您可以使用std::logic_error


要关闭assert断言检查,您可以在包括<assert.h>之前定义符号NDEBUG

此头文件具有特殊支持,以便您可以多次包含它,带或不带已定义的NDEBUG

C++11 §17.6.2.2/2:

翻译单元可以按任何顺序包括库头文件(第2条)。每个头文件可以包含多次,与仅包含一次没有区别,除了每次包含<cassert><assert.h> 的影响取决于当前词汇定义的NDEBUG

上述讨论的合理定义类似地取决于NDEBUG而没有包含保护,例如:

文件assert_should_never_get_here.hpp

#include <stdexcept>        // std::logic_error
#include <assert.h>

#undef ASSERT_SHOULD_NEVER_GET_HERE
#ifdef NDEBUG
#   define ASSERT_SHOULD_NEVER_GET_HERE() \
        throw std::logic_error( "Reached a supposed unreachable point" )
#else
#   define ASSERT_SHOULD_NEVER_GET_HERE() \
        do{ \
            assert( "Reached a supposed unreachable point" && 0 ); \
            throw 0; \
        } while( 0 )
#endif

免责声明:虽然我在2000年代早期已经编写过类似的代码,但我为了这个答案而编写了上面的代码,并且尽管我使用g++测试过它,但它可能并不完美。


(1)参见Basile Starynkevitch 的答案,讨论另一种可能性——适用于 g++ 的内置函数 __builtin_unreachable


1
很好注意到断言可以被禁用:你的代码流程应该与有或没有断言时相同! - edmz

18

它会一直失败。基本上就是这样。它失败的原因和“assert(x==5)”在x等于5时成功的原因一样。

如果你问的是一个应用程序,那么你应该将其放在代码块中,这种情况确实不应该发生。

switch(suit) {
  case CLUB:
  case DIAMOND:
  case HEART:
  case SPADE:
  // ...
  default:
    assert(0);
 }

但通常来说,断言是一个条件,对吧?那么如果只有“0”是什么意思呢? - Smith Western
2
@SmithWestern 零是一个永远为假的条件,就像“现在的时间是现在”这个条件永远为真一样。 - djechlin
2
应该是“每当 x 不等于 5”。 - user253751
为什么每当x=5时,assert(x==5)会失败?这应该是正确的。 - Smith Western
2
@SmithWestern 在 C 语言中没有布尔类型,因此你可以用零表示“假”,任何非零值表示“真”。 - tonytony
显示剩余7条评论

13

是的,它总会失败。

assert(0)assert(false) 通常用于标记无法到达的代码,因此在调试模式下发出诊断消息并在实际到达无法到达的位置时中止程序,这是程序没有按照我们认为的方式运行的明确信号。


2
不是“无法到达”,而是“只有在我的代码中存在错误时才无法到达”。无法到达的代码是无法到达的。可惜,“只有在我的代码中存在错误时才无法到达”却经常发生 :-( - gnasher729

5
除了其他答案(特别是这个),如果您使用的是最新版本的GCC(或Clang),您可能考虑使用一些GCC内置函数,特别是__builtin_unreachable(),而不是assert(0)

有一些区别:首先,assert可以使用-DNDEBUG禁用。而__builtin_unreachable将改变编译器优化代码的方式。

当然,有些编译器不知道__builtin_unreachable

您还可以考虑调用一些C++11或更高版本中的 [[noreturn]] C++函数 - 或对于GCC,可以使用__attribute__((noreturn)),例如abort()

顺便说一句,assert(0)并不完全像抛出异常(因为异常可以被捕获)。


断言会引发一个信号,如果我没记错的话,这个信号实际上是可以被捕获的。 - edmz
@black:它调用了 abortabort 的操作是否可以被拦截是一个实现细节。 - Cheers and hth. - Alf
不,如果我没记错的话,在实现assert(或abort)时,在引发信号之前会将信号处理器重置为默认行为。 - Basile Starynkevitch
原来你可以捕获信号,但仍然无法从中返回,即程序将被终止;abort确实标记为[[noreturn]]。所有这些都可以在man页面中找到。@Cheersandhth.-Alf:此行为似乎甚至是明确定义的。 - edmz
2
_builtin_unreachable 是非常不同的东西。assert(0) 的意思是“如果代码执行到这里,那么我的代码中存在错误,请告诉我和/或停止程序”。__builtin_reachable 的意思是“这段代码是不可达的。编译器可能无法智能地理解它,但请相信我,它是不可达的。”编译器将删除以下代码,因为它是不可达的;如果函数返回值不可达,则不会报错,等等。 - gnasher729
是的,但我在第二段已经说过了。 - Basile Starynkevitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接