noreturn 的意义何在?

Posted

技术标签:

【中文标题】noreturn 的意义何在?【英文标题】:What is the point of noreturn? 【发布时间】:2012-05-19 06:41:32 【问题描述】:

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

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

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

那么,noreturn 属性的意义何在?应该怎么用?

【问题讨论】:

这种功能(在程序执行中很可能发生一次)有什么重要的地方值得关注?这不是很容易发现的情况吗? @MrLister OP 将“返回”和“返回值”的概念混为一谈。鉴于它们几乎总是串联使用,我认为这种混淆是有道理的。 【参考方案1】:

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

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

【讨论】:

execve 这样不应该返回但可以返回的函数呢?它应该有 noreturn 属性吗? 不,它不应该——如果控制流有可能返回给调用者,它不能有noreturn 属性。 noreturn 只能在保证你的函数在控制流返回给调用者之前终止程序的情况下使用——例如因为你调用了 exit()、abort()、assert(0) 等。 这是否包括通过异常抛出返回(可以这么说),或者抛出的异常是否在 noreturn 函数之外跳过 catches,或者不允许从 noreturn 函数内抛出异常? @SlippD.Thompson 如果对 noreturn 函数的调用被包装在 try 块中,则来自 catch 块的任何代码都将被视为再次可达。 @SlippD.Thompson 不,不可能返回。抛出异常不会返回,所以如果每条路径都抛出,那么它就是noreturn。处理该异常与返回的异常不同。调用后try 中的任何代码仍然无法访问,如果不是void,则不会发生任何分配或使用返回值。【参考方案2】:

noreturn 不会告诉编译器该函数不返回任何值。它告诉编译器控制流不会返回给调用者。这允许编译器进行各种优化——它不需要保存和恢复调用周围的任何易失状态,它可以通过死代码消除任何可能跟随调用的代码,等等。

【讨论】:

小心使用 [[noreturn]]。因为如果函数包含 while 循环,而你无意中中断了循环,程序可能会执行连线操作。【参考方案3】:

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

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

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

【讨论】:

gcc/clang don't give warnings @TemplateRex: 用-Wno-return 编译你会得到一个警告。可能不是您所期望的,但可能足以告诉您编译器知道[[noreturn]] 是什么并且可以利用它。 (我有点惊讶-Wunreachable-code 没有加入......) @TemplateRex:对不起-Wmissing-noreturn,警告意味着流分析确定std::cout 不可访问。我手头没有足够新的 gcc 来查看生成的程序集,但如果对 operator&lt;&lt; 的调用被丢弃,我不会感到惊讶 这是一个assembly dump(coliru 中的-S -o - 标志),确实删除了“无法访问”的代码。有趣的是,-O1 is already enough 在没有 [[noreturn]] 提示的情况下删除了无法访问的代码。 @TemplateRex:所有代码都在同一个翻译单元中并且可见,因此编译器可以从代码中推断出[[noreturn]]。如果此翻译单元只有在其他地方定义的函数声明,编译器将无法删除该代码,因为它不知道该函数不会返回。这就是属性应该帮助编译器的地方。【参考方案4】:

以前的答案正确解释了 noreturn 是什么,但没有为什么它存在。我不认为“优化” cmets 是主要目的:不返回的函数很少见,通常不需要优化。相反,我认为 noreturn 存在的主要理由是避免误报警告。例如,考虑以下代码:

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

如果 abort() 没有被标记为“noreturn”,编译器可能会警告此代码的路径中 f 没有按预期返回整数。但是因为 abort() 被标记为 no return 它知道代码是正确的。

【讨论】:

列出的所有其他示例都使用 void 函数——当您同时拥有 [[no return]] 指令和非 void 返回类型时,它是如何工作的? [[no return]] 指令是否仅在编译器准备好警告可能不返回并忽略警告时才起作用?例如,编译器是否会运行:“好的,这是一个非 void 函数。” * 继续编译 * “哦,废话,这段代码可能不会返回!我应该警告用户吗?* “没关系,我看到了 no-return 指令。继续” 我示例中的 noreturn 函数不是 f(),而是 abort()。标记非空函数 noreturn 是没有意义的。有时返回值有时返回的函数(一个很好的例子是 execve())不能标记为 noreturn。 implicit-fallthrough 是另一个这样的例子:***.com/questions/45129741/…【参考方案5】:

从理论上讲,void 是其他语言中称为unittop 的类型。它的逻辑等价物是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()  throw exception(); 
...
int x = y!=0 ? 100/y : raise();

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

【讨论】:

没有任何东西可以转换为voidvoid 永远不会评估为truefalse 或其他任何东西。 当我说 true 时,我的意思不是“true 类型的值 bool”,而是逻辑意义,参见 Curry-Howard correspondence 不适合特定语言类型系统的抽象类型理论在讨论该语言的类型系统时是无关紧要的。有问题的问题:-) 是关于 C++,而不是类型理论。 正如答案所暗示的, (void)true; 完全有效。 void(true) 在语法上是完全不同的东西。它试图通过以true 作为参数调用构造函数来创建void 类型的新对象;除其他原因外,这失败了,因为 void 不是头等舱。 @JoshSanford 它解释了voidnoreturn 在类型理论术语中的区别,补充了上述(公认更有用的)实用答案。

以上是关于noreturn 的意义何在?的主要内容,如果未能解决你的问题,请参考以下文章

“void”函数中的NoReturn与None - 在Python 3.6中键入注释

“void”函数中的 NoReturn 与 None - Python 3.6 中的类型注释

《OpenGL编程指南第七版》学习——编译时提示“error C2381: “exit” : 重定义;__declspec(noreturn) 不同”错误的解决办法

`return;` 和 no return 有啥区别?

OpenGL编译问题随手记

Phan 给出内置 JetBrains PhpStorm 注释的问题