什么是不归路?


189

[dcl.attr.noreturn]提供以下示例:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

但我不明白这是什么意思[[noreturn]],因为该函数的返回类型已经存在void

那么,该noreturn属性的意义是什么?应该如何使用?


1
值得关注的这种功能(最有可能在程序执行中一次发生)有什么重要的意义?这不是一个容易发现的情况吗?
user666412

1
@MrLister OP将“返回”和“返回值”的概念混为一谈。考虑到它们几乎总是串联使用,我认为这种混淆是合理的。
Slipp D. Thompson

Answers:


209

noreturn属性应该用于不返回到调用方的函数。这并不意味着void函数(确实会返回调用者-它们只是不返​​回值),而是函数完成后控制流不会返回到调用函数的函数(例如,退出应用程序的函数,永久循环或抛出异常,如您的示例所示)。

编译器可以使用它进行一些优化并生成更好的警告。例如,如果f具有noreturn属性,则编译器可能会g()在您编写时警告您代码已死f(); g();。同样,编译器会知道在调用后不会警告您关于缺少return语句的警告f()


5
关于功能是什么,如execve不该回去,但是可能?它应该具有noreturn属性吗?
Kalrish 2014年

22
不,不应该-如果控制流有可能返回给调用者,则它必须没有noreturn属性。noreturn仅在保证您的函数在控制流返回到调用方之前可以执行某些终止程序的情况下,例如,因为您调用exit(),abort(),assert(0)等
。– RavuAlHemio

6
@ SlippD.Thompson如果将noreturn函数的调用包装在try块中,则catch块中的任何代码都将再次视为可访问。
sepp2k

2
@ sepp2k很酷。因此返回并非不可能,只是异常。这很有用。干杯。
Slipp D. Thompson

7
@ SlippD.Thompson不,不可能返回。引发异常不会返回,因此,如果每条路径都引发,则为noreturn。处理该异常与返回异常不同。try调用后的任何代码仍然无法访问,否则void将不会发生任何赋值或使用返回值的情况。
乔恩·汉娜

62

noreturn不会告诉编译器该函数不返回任何值。它告诉编译器控制流不会返回给调用者。这使编译器可以进行各种优化-不需要保存和恢复调用周围的任何易失状态,可以使代码死代码以消除将在调用之后出现的任何代码,等等。


29

这意味着该功能将无法完成。调用以下命令后,控制流将永远不会命中该语句f()

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

该信息可以由编译器/优化器以不同方式使用。编译器可以添加一条警告,指出上面的代码不可访问,并且它可以g()以不同的方式修改实际代码,例如以支持延续。


3
gcc / clang 不发出警告
TemplateRex

4
@TemplateRex:使用编译,-Wno-return您会收到警告。可能不是您所期望的那样,但是告诉您编译器已经了解了什么[[noreturn]]并且可以利用它就足够了。(我有点惊讶的是-Wunreachable-code没踢...)
大卫·罗德里格斯- dribeas

3
@TemplateRex:很抱歉-Wmissing-noreturn,该警告表示流分析确定std::cout无法访问。我不手边有一个新的gcc足以看生成的程序集,但如果调用我不会感到惊讶operator<<
大卫·罗德里格斯- dribeas

1
这是一个程序集转储(coinru中的-S -o-标志),的确删除了“无法访问的”代码。有趣的-O1是,已经足够在没有[[noreturn]]提示的情况下删除该无法访问的代码。
TemplateRex

2
@TemplateRex:所有代码都在同一翻译单元中并且可见,因此编译器可以[[noreturn]]从代码中推断出。如果此转换单元仅具有在其他地方定义的函数的声明,则编译器将无法删除该代码,因为它不知道该函数不会返回。这是该属性应有助于编译器的地方。
大卫·罗德里格斯(DavidRodríguez)-dribeas

17

先前的答案正确地解释了什么是归还,但没有解释为什么它存在。我不认为“优化”注释的主要目的是:不返回的函数很少,通常不需要进行优化。相反,我认为不归还的主要理由是避免出现假阳性警告。例如,考虑以下代码:

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

如果不将abort()标记为“ noreturn”,则编译器可能已警告该代码具有f不会按预期返回整数的路径。但是因为abort()被标记为没有返回,所以它知道代码是正确的。


列出的所有其他示例都使用void函数-当您同时具有[[no return]]指令和非void返回类型时,它如何工作?[[no return]]指令仅在编译器准备警告可能不返回并忽略警告时才起作用吗?例如,编译器执行以下操作:“好吧,这是一个非void函数。” *继续编译*“糟糕,此代码可能不会返回!我应该警告用户吗?*”没关系,我看到了no-return指令。继续
罗利

2
在我的示例中,noreturn函数不是f(),而是abort()。标记非空函数noreturn没有意义。有时会返回值而有时会返回的函数(一个很好的例子是execve())不能标记为noreturn。
Nadav Har'El


11

从理论上讲,类型void是其他语言unit或所称的top。其逻辑等效为True。任何值都可以合法地强制转换为void(每种类型都是的子类型void)。将其视为“宇宙”集合;世界上所有值都没有通用的运算,因此对type值没有有效的运算void。换句话说,告诉您某物属于Universe集不会给您任何信息-您已经知道了。所以以下是声音:

(void)5;
(void)foo(17); // whatever foo(17) does

但是下面的分配不是:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]],另一方面,有时也被称为emptyNothingBottomBot和是的逻辑等效。它根本没有值,并且该类型的表达式可以强制转换为任何类型(即为该类型的子类型)。这是空集。请注意,如果有人告诉您“表达式foo()的值属于空集”,则它的信息量很高 -它告诉您该表达式将永远无法完成其正常执行;它会中止,抛出或挂起。与完全相反void

因此以下内容没有任何意义(伪C ++,因为noreturn它不是一流的C ++类型)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

但是下面的分配是完全合法的,因为throw编译器将其理解为不返回:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

在理想情况下,您可以将noreturn上述函数用作返回值raise()

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

遗憾的是,C ++可能出于实际原因不允许这样做。相反,它使您能够使用[[ noreturn ]]属性,该属性有助于指导编译器优化和警告。


5
不得对或或其他任何内容进行强制转换voidvoid也永远不能对其进行评估。truefalse
清晰的时间2015年

5
我说的true不是逻辑上true的“类型的值bool”,而是逻辑上的含义,请参阅库里-霍华德对应
Elazar

8
与特定语言的类型系统不符的抽象类型理论在讨论该语言的类型系统时是无关紧要的。问题(-)涉及的是C ++,而不是类型理论。
清晰

8
(void)true;正如答案所示,它是完全有效的。void(true)从语法上讲是完全不同的东西。试图void通过调用带有true参数的构造函数来创建新类型的对象。除其他原因外,这失败了,因为void它不是一流的。
Elazar

2
就是这样 我习惯于使用type(value)强制转换,而不是c样式强制转换。
清晰的时间2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.