为什么"noreturn"函数会返回?

msc*_*msc 71 c assembly function-call c11 noreturn

我读了关于属性的这个问题noreturn,它用于不返回调用者的函数.

然后我用C做了一个程序.

#include <stdio.h>
#include <stdnoreturn.h>

noreturn void func()
{
        printf("noreturn func\n");
}

int main()
{
        func();
}
Run Code Online (Sandbox Code Playgroud)

并使用以下代码生成代码的汇编:

.LC0:
        .string "func"
func:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $.LC0, %edi
        call    puts
        nop
        popq    %rbp
        ret   // ==> Here function return value.
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        call    func
Run Code Online (Sandbox Code Playgroud)

为什么函数func()在提供noreturn属性后返回?

Sou*_*osh 120

C中的函数说明符是编译器的提示,接受程度是实现定义的.

首先,_Noreturn函数说明符(或者,noreturn使用<stdnoreturn.h>)是编译器关于程序员将永远不会返回的理论承诺的提示.基于此承诺,编译器可以做出某些决定,对代码生成执行一些优化.

IIRC,如果用函数说明符指定的noreturn函数最终返回其调用者

  • 通过使用和明确的return声明
  • 到达功能体的末端

行为是不确定的.你不能从函数返回.

为清楚起见,使用noreturn函数说明符不会停止返回其调用者的函数表单.它是程序员对编译器的承诺,允许它更自由地生成优化代码.

现在,如果您早先和之后做出承诺,选择违反此规则,结果是UB.当_Noreturn函数似乎能够返回其调用者时,鼓励但不要求编译器产生警告.

根据章节§6.7.4 C11,第8段

使用_Noreturn函数说明符声明的函数不应返回其调用者.

并且,第12段,(注意评论!!)

EXAMPLE 2
_Noreturn void f () {
abort(); // ok
}
_Noreturn void g (int i) { // causes undefined behavior if i <= 0
if (i > 0) abort();
}
Run Code Online (Sandbox Code Playgroud)

因为C++,行为非常相似.引自第7.6.4节,C++14第2段(强调我的)

如果f调用函数f先前使用该noreturn属性声明并f最终返回,则行为未定义. [注意:该函数可以通过抛出异常来终止. - 尾注]

[注意:如果标记的函数[[noreturn]]可能返回,则鼓励实现发出警告. - 尾注]

3 [例如:

[[ noreturn ]] void f() {
throw "error"; // OK
}
[[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0
if (i > 0)
throw "positive";
}
Run Code Online (Sandbox Code Playgroud)

- 末端的例子]

  • tl; dr:`noreturn`并不意味着"这个函数不会返回"; 它意味着"告诉编译器它可以假设此函数不会返回".它不是为了让_your_工作变得更容易,而是让_compiler's_工作变得更容易. (36认同)
  • @MartinJames - 此功能对静态分析工具有很大的好处,特别是那些执行最坏情况堆栈深度分析的工具.没有真正不会返回的函数的`noreturn`属性,这些工具的结果将是不正确的.它*也可以*有助于优化(每次调用这样的函数时最多保存几个字节),因为编译器不必考虑返回的函数; 调用函数内的控制流在此时停止. (20认同)
  • @MartinJames:用这种方式思考:确定一个函数是否会返回是*字面上*停止问题.然而,它也是一个有趣的属性,无论是正确性还是优化.那么,当我们有一个我们想要决定的属性但实际上是*陈规定型的不可判定属性时,我们能做什么呢?我们要问程序员.请注意,具有比C更具表现力的类型系统的语言通常也具有此类型.例如,Scala为一个不返回任何内容的函数键入`Unit`,并为不返回的函数键入`Nothing`. (15认同)
  • @MartinJames请不要责怪我先生,我与这个......特征的"存在"无关.:) (4认同)
  • @MauganRa这就是为什么我建议`ud2`,IMO这个(中止程序,在某些其他可以被静态证明是未定义行为的情况下完成同样的事情)比返回已经优化的调用者更"好"假设它不会. (4认同)

And*_*nle 48

为什么在提供noreturn属性后函数func()会返回?

因为你编写的代码告诉了它.

如果你不想让你的函数返回,请致电exit()abort()或相似的,所以它不会返回.

什么别的将你的函数做的比其他的回报,它已被称为后printf()

C标准6.7.4功能说明符,段落12具体包括的一个例子noreturn,实际上可以返回功能-并标记行为未定义:

例2

_Noreturn void f () {
    abort(); // ok
}
_Noreturn void g (int i) {  // causes undefined behavior if i<=0
    if (i > 0) abort();
}
Run Code Online (Sandbox Code Playgroud)

总之,noreturn是一个限制放置你的代码-它告诉编译器"我的代码永远不会返回".如果你违反了这个限制,那一切都在你身上.

  • @phonetagger实际上,随着时间的推移,你可以说"是的,我知道,严格来说它是未定义的,但实际上没有太糟糕的事情会发生"的次数越来越少.编译器变得越聪明,如果你给他们任何借口,他们似乎有更多的余地让自己去做真正疯狂的事情. (7认同)
  • @phonetagger对于包含未定义行为的程序来说,及时发送信号并在生成程序之前杀死编译器是完全合法的.(*物理*可能不允许这样做,但标准没有问题.) (6认同)
  • @phonetagger我们不使用幻想(和*令人难忘的*)的例子,因为我们实际上认为UB会让时间旅行的紫龙将鼻子的恶魔追逐到爆炸的冰箱里.我们这样做是为了让人们不要认为UB*必须以某种方式工作,因为所有其他选择都是荒谬的.*"即使是荒谬的结果也是可能的,没有任何假设是安全的"*是我们想要了解的想法.一些UB表现真的很奇怪,没有人会期待它们.(我甚至没有写这个评论;我刚刚运行了一个包含`i = i ++;`并且**出现*的程序.) (4认同)
  • @phonetagger:这是令人惊讶的UB的一个真实例子:https://godbolt.org/g/uZibdL最近的clang ++编译非void函数,它们在没有`return`的情况下结束无限循环,显然是为了帮助你捕获错误你错过了警告(或者可能没有警告的情况,因为它不确定是否会采取路径).它不会为C执行此操作,因为[如果调用者使用结果,它只是C99/C11中的UB](/sf/ask/1442999771/ -void功能 - 不 - 不返回一个值#comment30891689_20622422). (3认同)
  • 相比之下,安德鲁在开场白中直接回答了OP的问题。答案不是“因为它是UB,它可以做任何事情”;圣牛,它有随机的机会,它回来了!真是“走运”!答案是,因为OP的代码说要这样做,所以返回了。当然,只要不提及UB,任何答案都会被忽略,Andrew也这样做。但按目前的情况来看,Sourav的编辑答案现在是更好的答案。 (2认同)

Ste*_*mit 25

noreturn是一个承诺.你告诉编译器,"它可能会也可能不会很明显,但知道,基于我编写代码的方式,这个函数永远不会返回." 这样,编译器可以避免设置允许函数正确返回的机制.省略这些机制可能允许编译器生成更有效的代码.

一个函数怎么能不返回?一个例子是,如果它打电话exit().

但是如果你向编译器承诺你的函数不会返回,并且编译器没有安排它可以使函数正确返回,然后你去编写一个确实返回的函数,那么编译器应该怎样做?它基本上有三种可能性:

  1. 对你"好",并想办法让函数无论如何都能正常返回.
  2. 发出代码,当函数不正确地返回时,它会以任意不可预测的方式崩溃或表现.
  3. 给你一个警告或错误信息,指出你违背了你的诺言.

编译器可能会执行1,2,3或某种组合.

如果这听起来像未定义的行为,那是因为它是.

编程与现实生活中的底线是:不要做出你无法保留的承诺.其他人可能会根据您的承诺做出决定,如果您违背承诺,可能会发生不好的事情.


Gro*_*roo 15

noreturn属性是向编译器提供有关函数的承诺.

如果你这样做,从这样一个函数返回,行为是不确定的,但是这并不意味着一个成熟的编译器可以让你乱了应用程序的状态完全通过移除ret声明,特别是因为编译器会经常甚至可以推断出回归确实是可能的.

但是,如果你这样写:

noreturn void func(void)
{
    printf("func\n");
}

int main(void)
{
    func();
    some_other_func();
}
Run Code Online (Sandbox Code Playgroud)

那么编译器some_other_func完全删除它是完全合理的,如果感觉就好了.


nne*_*neo 11

正如其他人所提到的,这是经典的未定义行为.你承诺func不会回来,但无论如何你都让它回来了.当你休息时,你可以拿起碎片.

虽然编译器func以通常的方式编译(尽管你的noreturn),但noreturn影响调用函数.

您可以在装配清单中看到这一点:编译器承担,中main,那func将不会返回.因此,它确实删除了之后的所有代码call func(请参阅https://godbolt.org/g/8hW6ZR).汇编列表没有被截断,它实际上只是在之后结束,call func因为编译器假定之后的任何代码都无法访问.因此,当func实际返回时,main将开始执行跟随main函数的任何垃圾- 无论是填充,立即常量还是大量00字节.再次 - 非常未定义的行为.

这是可传递的 - noreturn在所有可能的代码路径中调用函数的函数本身可以被假定为noreturn.


小智 8

根据这个

如果声明_Noreturn的函数返回,则行为未定义.如果可以检测到,则建议使用编译器诊断.

程序员有责任确保此函数永不返回,例如函数末尾的exit(1).


For*_*Bru 6

ret只是意味着该函数将控制权返回给调用者.因此,main确实call func,CPU执行的功能,然后用ret,CPU继续执行main.

编辑

因此,事实证明,noreturn不会使函数根本不返回,它只是一个说明器,告诉编译器该函数的代码是以函数不会返回的方式编写的.所以,你应该做的是确保这个函数实际上不会将控制权返回给被调用者.例如,你可以exit在里面打电话.

另外,鉴于我所读到的关于这个说明符的内容,似乎为了确保函数不会返回到它的调用点,应该调用其中的另一个 noreturn函数并确保后者始终运行(按顺序)避免未定义的行为)并且不会导致UB本身.

  • @ user694733我相信问题的核心是提问者希望"noreturn"做一些阻止他的代码返回的东西,而不是"noreturn"实际上对他编写的代码允许在标准下做什么的限制*. (3认同)

P__*_*J__ 6

没有返回功能不会保存条目上的寄存器,因为它不是必需的.它使优化更容易.例如,非常适合调度程序例程.

请参阅此处的示例:https: //godbolt.org/g/2N3THC并找出差异