noreturn是什么意思?

241

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

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

但我不明白[[noreturn]]的意义在哪里,因为该函数的返回类型已经是void

那么,noreturn属性有什么作用?应该如何使用它?


2
这种函数(在程序执行中很可能只会发生一次)有什么重要性值得如此关注吗?这不是一个很容易检测到的情况吗? - user666412
3
@MrLister,楼主混淆了“返回”和“返回值”的概念。考虑到它们几乎总是一起使用,我认为这种困惑是合理的。 - Slipp D. Thompson
好问题和答案。但我喜欢你提到的参考链接:https://dev59.com/4Wkv5IYBdhLWcg3wghIi - QT-1
6个回答

278

[[noreturn]] 属性用于表示某些函数不会返回到调用者。这并不意味着这些函数是 void 函数(这些函数会返回到调用者,只是它们不返回值),而是指函数在完成后控制流不会返回到调用函数(例如退出应用程序、无限循环或像您的示例中抛出异常的函数)。

编译器可以使用这个属性进行一些优化并生成更好的警告。例如,如果函数 f 具有 [[noreturn]] 属性,则当您编写 f(); g(); 时,编译器可以警告您有关 g() 是死代码的情况。同样,编译器也将知道在调用 f() 后不需要警告您缺少 return 语句的情况。


7
execve这样的函数,它本应该不返回结果但实际上有可能返回,是否应该加上_noreturn_属性?请问您的想法是什么? - djsp
30
不应该使用noreturn属性,如果控制流可能返回给调用者,则必须不使用它。只有在您的函数保证执行一些会在控制流返回给调用者之前终止程序的操作时才可以使用noreturn属性--例如,因为您调用了exit(),abort(),assert(0)等。 - RavuAlHemio
2
这是否包括通过异常抛出(可以这么说)返回,或者抛出的异常跳过noreturn函数外的catch,或者从noreturn函数内部抛出异常是不允许的? - Slipp D. Thompson
7
如果调用一个noreturn函数被包裹在try块中,那么从catch块开始的任何代码都将重新变为可达。 - sepp2k
8
@SlippD.Thompson 不,这是不可能返回的。抛出异常不是返回,所以如果每条路径都抛出异常,那么就是“noreturn”。处理该异常并不意味着它已经返回。在调用之后的try块中的任何代码仍然无法访问,并且如果不是void类型,则任何赋值或使用返回值的操作都不会发生。 - Jon Hanna
显示剩余6条评论

74

noreturn并不是告诉编译器函数没有返回值,而是告诉编译器控制流程不会返回给调用者。这使得编译器能够进行各种优化-在调用周围不需要保存和恢复任何易失性状态,可以消除任何在调用之后执行的死代码等等。


请注意使用 [[noreturn]]。因为如果函数包含 while 循环,并且您无意中中断了循环,程序可能会表现出奇怪的行为。 - CuteDoge
@CuteDoge 在这种情况下,编译器应该发出一条诊断信息,而不是 [[maybe_noreturn]]。例如,返回值的空函数会发出警告。如果你关闭或忽略了警告,那么你就会遇到其他问题。 - artless noise

33

之前的回答正确地解释了什么是noreturn,但没有解释它为什么存在。我不认为“优化”注释是其主要目的:不返回值的函数很少,并且通常不需要进行优化。相反,我认为noreturn的主要存在理由是避免错误的正向警告。例如,考虑以下代码:

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

如果abort()没有被标记为"noreturn",编译器可能会警告这段代码存在一条路径,f没有按预期返回一个整数。但是由于abort()被标记为不返回,所以编译器知道代码是正确的。


所有其他列出的示例都使用void函数--当您同时具有[[no return]]指令和非void返回类型时,它是如何工作的?当编译器准备警告可能不返回并忽略警告时,[[no return]]指令是否只起作用?例如,编译器是否会执行以下操作:“好的,这是一个非void函数。” 继续编译 “哦,该代码可能不会返回!我应该警告用户吗?” “没关系,我看到了无返回指令。继续” - Raleigh L.
7
示例中的noreturn函数不是f(),而是abort()。将非void函数标记为noreturn没有意义。有时返回值,有时返回(execve()是一个很好的例子)的函数不能被标记为noreturn。 - Nadav Har'El
1
implicit-fallthrough是另一个例子:https://dev59.com/fVcO5IYBdhLWcg3w3VLU#52707279 - Ciro Santilli OurBigBook.com

33

这意味着该函数不会完成。控制流永远不会触及调用f()后的语句:

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

这些信息可以被编译器/优化器用于不同的目的。编译器可以添加一个警告,表示上面的代码是无法到达的,并且它可以以不同的方式修改 g() 的实际代码,例如支持continuations。


3
GCC/Clang【不要发出警告】。 - TemplateRex
4
使用编译选项-Wno-return进行编译,将会得到一条警告。可能并非您所期望的那种警告,但这已足以表明编译器知道[[noreturn]]是什么并且可以利用它。(我有点惊讶-Wunreachable-code没有起作用...) - David Rodríguez - dribeas
3
@TemplateRex: 抱歉,"-Wmissing-noreturn"警告意味着流分析确定了"std::cout"无法到达。我手头没有足够新的gcc来查看生成的汇编代码,但如果调用"operator<<"被删除,我不会感到惊讶。 - David Rodríguez - dribeas
现在它可以工作了,而且还不错(基本上,[[noreturn]] 应该被中转应用到最外层的范围)。 - TemplateRex
1
这里有一个汇编转储(在coliru中使用-S -o-标志),确实删除了“不可达”的代码。有趣的是,-O1已经足够在没有[[noreturn]]提示的情况下删除该不可达代码。 - TemplateRex
2
@TemplateRex:所有代码都在同一个翻译单元中并且可见,因此编译器可以从代码中推断出[[noreturn]]。如果这个翻译单元只有一个在其他地方定义的函数声明,编译器将无法删除该代码,因为它不知道该函数是否返回。这就是属性应该帮助编译器的地方。 - David Rodríguez - dribeas

16

从类型理论的角度来说,void 在其他语言中被称为 unit 或者 top。它在逻辑上等价于 True。任何值都可以合法地转换为 void(每种类型都是 void 的子类型)。可以将其视为“宇宙”集合;世界上所有值没有共同的操作,所以对于类型为void的值没有有效的操作。换句话说,告诉你某些东西属于宇宙集合并不会给你任何信息 - 你已经知道了。因此以下是正确的:

(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,是逻辑上等价于False的。它没有任何值,该类型的表达式可以转换为(即为子类型)任何类型。这就是空集合。请注意,如果有人告诉您“表达式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()的返回值:

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

遗憾的是C++不允许使用无返回值函数。可能是出于实际原因。相反,它提供了[[noreturn]]属性,可以帮助指导编译器进行优化和警告。


8
任何内容都不能被转换成 void,而且 void 永远无法被评估为 truefalse 或其他任何东西。 - Clearer
8
当我说“true”时,我的意思并非布尔类型的值“true”,而是逻辑意义上的“真”,请参考柯里-霍华德对应 - Elazar
12
抽象类型理论如果不符合特定语言的类型系统,则在讨论该语言的类型系统时是无关紧要的。问题所涉及的是C++,而不是类型理论。 - Clearer
9
根据答案,(void)true; 是完全有效的。而 void(true) 在语法上是完全不同的东西。它试图通过使用 true 作为参数来调用一个构造函数来创建一个新的 void 类型对象;这会失败,其中一个原因是 void 不是一等类型。 - Elazar
4
它解释了“void”和“noreturn”在类型理论术语上的区别,以补充上面那些(虽然更实用)的实用回答。 - Elazar
显示剩余15条评论

-1
这是我对[[noreturn]]的观点,用非常简单和简短的术语来表达。
它是在C++11中引入的。 使用`[[noreturn]]`属性的函数根本不返回任何东西;这与返回`void funcs()`的函数不同。 要澄清的是,你不能在void函数中使用`[[noreturn]]`属性。 它主要是出于性能原因引入的。当一个函数被标记为`[[noreturn]]`属性时,编译器知道它不必返回给调用者;它不会生成任何代码;因此有一些小的性能优势。 突出的用例有: - 用于抛出错误/异常: ```cpp #include [[noreturn]] void foo() { throw std::logic_error("error"); }
void bar() { try { foo(); std::cout << "Hello"; // 永远不会在此处返回,因此永远不会被打印 } catch (std::logic_error &e) { std::cout << "World"; } }
int main() { bar(); return 0; } ``` - 用于退出代码: ```cpp #include #include [[noreturn]] void foo() { std::cerr << "Error"; std::exit(EXIT_FAILURE); }
int main() { foo(); // 调用时打印Error和退出代码。 return 0; } ``` 如果函数实际上返回了某些东西,并且你已经为它定义了`[[noreturn]]`,它将产生未定义的行为以警告用户。例如: ```cpp #include [[noreturn]] int foo() { // 定义了[[noreturn]] return 411; // 返回整数(411) }
int main() { int k = foo(); std::cout << k; return 0; } ``` 编译器输出(GCC):(如上所述,显示未定义的行为) ```cpp : In function 'int foo()': :4:12: warning: function declared 'noreturn' has a 'return' statement 4 | return 411; | ^~~ :4:12: warning: 'noreturn' function does return 4 | return 411; | ^ ASM generation compiler returned: 0 : In function 'int foo()': :4:12: warning: function declared 'noreturn' has a 'return' statement 4 | return 411; | ^~~ :4:12: warning: 'noreturn' function does return 4 | return 411; | ^ Execution build compiler returned: 0 Program returned: 139 Program terminated with signal: SIGSEGV ```
希望这能帮到你。谢谢!

澄清一下,你不能在void函数中使用[[noreturn]]属性。是的,你可以这样做,你的第一个例子就是这样做的。 - undefined

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