Why does "noreturn" function return?

15,924

Solution 1

The function specifiers in C are a hint to the compiler, the degree of acceptance is implementation defined.

First of all, _Noreturn function specifier (or, noreturn, using <stdnoreturn.h>) is a hint to the compiler about a theoretical promise made by the programmer that this function will never return. Based on this promise, compiler can make certain decisions, perform some optimizations for the code generation.

IIRC, if a function specified with noreturn function specifier eventually returns to its caller, either

  • by using and explicit return statement
  • by reaching end of function body

the behaviour is undefined. You MUST NOT return from the function.

To make it clear, using noreturn function specifier does not stop a function form returning to its caller. It is a promise made by the programmer to the compiler to allow it some more degree of freedom to generate optimized code.

Now, in case, you made a promise earlier and later, choose to violate this, the result is UB. Compilers are encouraged, but not required, to produce warnings when a _Noreturn function appears to be capable of returning to its caller.

According to chapter §6.7.4, C11, Paragraph 8

A function declared with a _Noreturn function specifier shall not return to its caller.

and, the paragraph 12, (Note the comments!!)

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

For C++, the behaviour is quite similar. Quoting from chapter §7.6.4, C++14, paragraph 2 (emphasis mine)

If a function f is called where f was previously declared with the noreturn attribute and f eventually returns, the behavior is undefined. [ Note: The function may terminate by throwing an exception. —end note ]

[ Note: Implementations are encouraged to issue a warning if a function marked [[noreturn]] might return. —end note ]

3 [ Example:

[[ 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";
}

—end example ]

Solution 2

Why function func() return after providing noreturn attribute?

Because you wrote code that told it to.

If you don't want your function to return, call exit() or abort() or similar so it doesn't return.

What else would your function do other than return after it had called printf()?

The C Standard in 6.7.4 Function specifiers, paragraph 12 specifically includes an example of a noreturn function that can actually return - and labels the behavior as undefined:

EXAMPLE 2

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

In short, noreturn is a restriction that you place on your code - it tells the compiler "MY code won't ever return". If you violate that restriction, that's all on you.

Solution 3

noreturn is a promise. You're telling the compiler, "It may or may not be obvious, but I know, based on the way I wrote the code, that this function will never return." That way, the compiler can avoid setting up the mechanisms that would allow the function to return properly. Leaving out those mechanisms might allow the compiler to generate more efficient code.

How can a function not return? One example would be if it called exit() instead.

But if you promise the compiler that your function won't return, and the compiler doesn't arrange for it to be possible for the function to return properly, and then you go and write a function that does return, what's the compiler supposed to do? It basically has three possibilities:

  1. Be "nice" to you and figure out a way to have the function return properly anyway.
  2. Emit code that, when the function improperly returns, it crashes or behaves in arbitrarily unpredictable ways.
  3. Give you a warning or error message pointing out that you broke your promise.

The compiler might do 1, 2, 3, or some combination.

If this sounds like undefined behavior, that's because it is.

The bottom line, in programming as in real life, is: Don't make promises you can't keep. Someone else might have made decisions based on your promise, and bad things can happen if you then break your promise.

Solution 4

The noreturn attribute is a promise that you make to the compiler about your function.

If you do return from such a function, behavior is undefined, but this doesn't mean a sane compiler will allow you to mess the state of the application completely by removing the ret statement, especially since the compiler will often even be able to deduce that a return is indeed possible.

However, if you write this:

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

int main(void)
{
    func();
    some_other_func();
}

then it's perfectly reasonable for the compiler to remove the some_other_func completely, it if feels like it.

Solution 5

As others have mentioned, this is classic undefined behavior. You promised func wouldn't return, but you made it return anyway. You get to pick up the pieces when that breaks.

Although the compiler compiles func in the usual manner (despite your noreturn), the noreturn affects calling functions.

You can see this in the assembly listing: the compiler has assumed, in main, that func won't return. Therefore, it literally deleted all of the code after the call func (see for yourself at https://godbolt.org/g/8hW6ZR). The assembly listing isn't truncated, it literally just ends after the call func because the compiler assumes any code after that would be unreachable. So, when func actually does return, main is going to start executing whatever crap follows the main function - be it padding, immediate constants, or a sea of 00 bytes. Again - very much undefined behavior.

This is transitive - a function that calls a noreturn function in all possible code paths can, itself, be assumed to be noreturn.

Share:
15,924
msc
Author by

msc

Hey, I am Mahendra. I am passionate about programming. I am working in C, C++ and embedded. Thank you.

Updated on June 14, 2022

Comments

  • msc
    msc almost 2 years

    I read this question about noreturn attribute, which is used for functions that don't return to the caller.

    Then I have made a program in C.

    #include <stdio.h>
    #include <stdnoreturn.h>
    
    noreturn void func()
    {
            printf("noreturn func\n");
    }
    
    int main()
    {
            func();
    }
    

    And generated assembly of the code using this:

    .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
    

    Why does function func() return after providing noreturn attribute?

  • kocica
    kocica over 6 years
    Thats true but you should read this
  • ForceBru
    ForceBru over 6 years
    @FilipKočica, this is exactly what I was reading when you posted this comment
  • Andrew Henle
    Andrew Henle over 6 years
    @user694733 I believe the core of the question is that the questioner expected noreturn to do something that prevented his code from returning, instead of noreturn actually placing restriction on what the code he wrote is allowed to do under the standard.
  • Groo
    Groo over 6 years
    @MartinJames: I feel the same about gcc pure/const attributes, which basically just state that the developer promises to make a function which will not do any side effects, or nonnull which is a promise that you won't pass a null pointer to the function so the compiler (allowing it to remove any null checks you might do inside the function).
  • cmaster - reinstate monica
    cmaster - reinstate monica over 6 years
    Actually, the only thing that can be optimized by noreturn is a few(!) bytes from the executable size: noreturn functions can never be part of the performance critical path in any code that's at least half sane. The whole construct is much more about silencing unnecessary warnings than about optimization.
  • 0___________
    0___________ over 6 years
    @cmaster no attributes make code sane, only the knowledge of the coder.
  • cmaster - reinstate monica
    cmaster - reinstate monica over 6 years
    That's not what I said. What I said is: If the code is at least half sane, that implies that there is zero performance impact of noreturn. That's the opposite direction of reasoning from what you seem to have understood. I would never say that use of noreturn makes code more or less sane.
  • 0___________
    0___________ over 6 years
    That's not what I said. You have too complicated way of thinking for me. I do not catch that "half sane" thought
  • phonetagger
    phonetagger over 6 years
    @MartinJames - This feature is of immense benefit to static analysis tools, especially ones that perform worst-case stack depth analysis. Without the noreturn attribute on a function that truly won't return, the results of such tools would be incorrect. It can also be helpful for optimizations (saving a few bytes at most, every time such a function is called) because the compiler does not have to account for the function returning; control flow within a calling function simply ceases at that point.
  • phonetagger
    phonetagger over 6 years
    Though Sourav's answer provides a more complete answer from a technical standpoint, this answer captures the real issue more succinctly. In my opinion THIS is the best answer! "Because you wrote code that told it to."
  • cmaster - reinstate monica
    cmaster - reinstate monica over 6 years
    I simply had the problem that writing "noreturn functions can never be part of the performance critical path" would have been wrong: It is perfectly possible to write a program that contains noreturn calls in the performance critical path (by means of longjmp()). However, those programs will inevitably be braindead. The condition "code that's at least half sane" simply excludes those. Hope that clarifies things. (Sorry for my confusingly precise language.)
  • Sourav Ghosh
    Sourav Ghosh over 6 years
    @phonetagger no doubt, this is a very targeted answer and perfectly to the point. The bigger problem in the question is, with a piece of code involving UB, there is no guarantee that it'll build to produce a binary, at all. Treat all warnings as errors and you won't be having a return, at all. :)
  • phonetagger
    phonetagger over 6 years
    @SouravGhosh – I disagree about no guarantee that it’ll produce a binary at all, unless (as you suggest) the compiler is set to treat all warnings as errors. "UB" is with respect to the compiled executable, not with respect to the compiler itself. But the question was simple: "Why does function func() return after providing noreturn attribute?" Your answer is relevant, but doesn't really answer the question per se. This does: "Because you wrote code that told it to." Yours answers a hypothetical different question: “Why does my noreturn function crash the program when it returns?”
  • Hong Ooi
    Hong Ooi over 6 years
    tl;dr: noreturn doesn't mean "this function won't return"; it means "tell the compiler it can assume this function won't return". It's not there to make your job easier, it's there to make the compiler's job easier.
  • Jörg W Mittag
    Jörg W Mittag over 6 years
    @MartinJames: Think about it this way: figuring out whether a function will return or not is literally the halting problem. It is, however, also an interesting property, both for correctness and for optimization. So, what can we do when we have a property that we want to decide but is actually the stereotypical undecidable property? We have to ask the programmer. Note that languages with more expressive type systems than C usually also have types for this. E.g. Scala has type Unit for a function that returns nothing and type Nothing for a function that doesn't return.
  • Theodoros Chatzigiannakis
    Theodoros Chatzigiannakis over 6 years
    @MartinJames Look into bottom types for more information.
  • user2357112
    user2357112 over 6 years
    "Should" and "encouraged" are not requirements. "Must" is a requirement. Compilers are not required to warn for this.
  • Random832
    Random832 over 6 years
    Why does func emit a ret instruction (instead of ud2 or nothing)?
  • MauganRa
    MauganRa over 6 years
    @Random832 Because the compiler decided to be "nice": without ret the program would "loop" (actually recurse) infinitely and eventually exhaust stack space because each call allocates a new stack frame. See @Steve Summit's answer for what else the compiler could arrange to be done in such a case.
  • Random832
    Random832 over 6 years
    @MauganRa That was why I suggested ud2, IMO this (aborting the program, the same thing is done in certain other cases that can be statically proven to be undefined behavior) would be "nicer" than returning into a caller that's been optimized to assume it won't.
  • Solomon Ucko
    Solomon Ucko over 6 years
    About messing with the state of the application, at some point I made a program where the main function had no return statement, and the only way to close it was by logging out (it wouldn't close it self, I couldn't X it out (there was a blank graphics window for some reason), I couldn't force quit it via Task Manager nor terminal...). BTW, this was on Windows, not sure what compiler.
  • MauganRa
    MauganRa over 6 years
    @Random823 That would indeed be the most beneficial thing, along with a warning during compilation.
  • TripeHound
    TripeHound over 6 years
    @Random832 Because – if I'm reading other comments correctly – the use of noreturn doesn't make the function not return, it just tells the compiler that it can assume that the function will never return. In this instance, the author of the code is clearly lying through their teeth!
  • chepner
    chepner over 6 years
    Am I mistaken in assuming this would be helpful with tail-call optimization or continuation-passing style? If the function won't return, you don't need to save a return address when the function is called, and the stack frame could be reused by the last function called in the non-returning function.
  • Random832
    Random832 over 6 years
    @TripeHound I was suggesting that it assume that the function will never return while generating code for the function itself (as if __builtin_unreachable were called at the end). Though an illegal instruction trap would be helpful for catching the "impossible" case at runtime and not add much cost (only two extra bytes).
  • TripeHound
    TripeHound over 6 years
    I guess a compiler could do that (and perhaps would be helpful if it did) since in both C/C++ a returning noreturn is UB. I suspect a possible reason many/most/all don't (apart from the fact they're not mandated to) is that noreturn is a hint to the compiler, but "the code is the code" and if the latter (albeit incorrectly) performs a return, that trumps what the hint says.
  • Ray
    Ray over 6 years
    @phonetagger It is perfectly legal for a program containing undefined behavior to send a signal back in time that kills the compiler before it can produce the program. (This may not be permitted by physics, but the standard has no problem with it.)
  • phonetagger
    phonetagger over 6 years
    @Ray - Legal perhaps, but not realistic, even if such were permissible by physics. People on SO are wont to make up wild examples of what might result from UB, from purple dragons or little demons coming out of your nostrils to exploding refrigerators and time warps. If we’re realistic though, we can predict most of the likely results of UB, even though they may vary from one environment/build to another.
  • Jens
    Jens over 6 years
    In other words, don't lie to the compiler.
  • Steve Summit
    Steve Summit over 6 years
    @phonetagger Actually, as time goes on, the number of times you can say "yeah, I know, strictly speaking it's undefined, but in practice nothing too bad will happen" keeps getting smaller and smaller. The smarter the compilers get, the more leeway they seem to grant themselves to do really crazy stuff if you give them any excuse.
  • phonetagger
    phonetagger over 6 years
    @SteveSummit - I have never casually sat back in my reclining office chair, pulled up my spittoon, and said "Yeah, I know," (spit) "strickly speakin', it's underfined, but in prackiss, nuttin' too back never happint when we do it dat way. Oh, sorry 'bout missin' the 'toon, here's a hanky for yer shoe." On the contrary, I am saying that outlandish examples of what might result from UB may be funny, but they do not help confused new programmers better understand the machine within which their programs run. Real UB always has reasonable explanations if you dive into the resultant assembly code.
  • Peter Cordes
    Peter Cordes over 6 years
    @phonetagger: Here's a real example of surprising UB: godbolt.org/g/uZibdL recent clang++ compiles non-void functions that end without a return into infinite loops, apparently on purpose to help you catch the error if you missed the warning (or maybe cases where it didn't warn because it wasn't sure that path would ever be taken). It doesn't do this for C because it's only UB in C99/C11 if the caller uses the result.
  • Peter Cordes
    Peter Cordes over 6 years
    Huh, that's weird. noreturn seems to disable tailcall optimization (as well as stopping inlining if the noreturn function calls printf). You can see this even with -O3: godbolt.org/g/vFWp8E. Clang 4.0 and gcc 7.2 are the same here.
  • Peter Cordes
    Peter Cordes over 6 years
    @cmaster: this answer's example of a kernel's schedule() function is a perfect example. It ends with a context switch to whatever should run next, and tuning the crap out of it is something that people really do.
  • Peter Cordes
    Peter Cordes over 6 years
    Here's a Godbolt compiler explorer link using the diff-view feature (using a CPP macro and -D compiler option to build the same source with/without _Noreturn). godbolt.org/g/ocX4fS. Interestingly, gcc6.3 for x86 -m32 still saves registers before using. I guess different targets have to implement this optimization separately? (BTW, in answers, prefer using non-shortened Godbolt compiler-explorer links, to avoid link-rot. It's much better if you actually copy the asm you want to talk about into your answer, too).
  • Peter Cordes
    Peter Cordes over 6 years
    Oops, left out -Wall. See godbolt.org/g/8QEPBT. I'm wondering if this "feature" is actually a bug, because I'm only seeing it on ARM. I simplified the code a lot by using a call to an external function to clobber registers, so it has to save/restore the function-arg register across that call on MIPS/PowerPC/ARM64/x86 as well. Only on ARM32 does it overwrite a call-preserved register without saving it first. ARM32's push/pop instructions are special (multiple regs in one insn), so it's plausible that ARM is different whether it's an optimization or a bug.
  • Peter Cordes
    Peter Cordes over 6 years
    Ah, it's because gcc's noreturn handling specifically disables tailcall optimization for better abort() backtraces: gcc.gnu.org/bugzilla/show_bug.cgi?id=55747#c4.
  • phonetagger
    phonetagger over 6 years
    @PeterCordes - I don't find that surprising UB; it seems like a good proactive diagnostic tool for people who haven't learned to use -Werror to treat warnings as errors (or can't due to legacy issues). But regardless of whether one might find that behavior surprising, you'll note that it's in the realm of reasonable, not the realm of fanciful. My complaint with typical SO examples of UB is that they almost invariably wander into fanciful and unrealistic examples. While those may be funny, they miss the fact that there is always a mundane explanation for the actual behavior observed.
  • Ray
    Ray over 6 years
    @phonetagger We don't use the fanciful (and memorable) examples because we actually think that UB will cause time traveling purple dragons to chase the nasal demons into an exploding fridge. We do it to keep people from assuming that the UB must work a certain way because all the other options are ludicrous. "Even ludicrous outcomes are possible and no assumptions are safe" is the idea we want to get across. Some UB manifestations are so genuinely weird that nobody would expect them. (I didn't even write this comment; I just ran a program containing i = i++; and it appeared.)
  • cmaster - reinstate monica
    cmaster - reinstate monica over 6 years
    @PeterCordes schedule() is definitely not in any performance critical path: Context switches happen in time steps of around milliseconds, the "optimizations" we are talking about save time in the range of nanoseconds. That's a factor on the order of a million (very roughly). Just switching the virtual address space, and consequently invalidating the TLB is so expensive that its costs easily dwarve the gains of optimizing noreturn functions. And that's in kernel space. In user space, where most code is deployed, there is even less optimization potential.
  • Peter Cordes
    Peter Cordes over 6 years
    @cmaster: You're right, noreturn only applies once you've decided that you do need to context switch, and that's once per call. Optimizing the scheduler is a thing, but that's for scalability with many tasks. I was also thinking of the voluntary pre-emption use-case, where a kernel function might call the scheduler while doing something slow. But if the scheduler might just return (or return after context switching to something else and back), then that function isn't noreturn after all.
  • Peter Cordes
    Peter Cordes over 6 years
    So I agree, it's hard to imagine a sane case where it makes much of a performance difference. It could help for code-size if you have helper functions that throw exceptions. But if there's an exception throw/catch in a hot loop, you're doing something wrong.
  • Peter Cordes
    Peter Cordes over 6 years
    The chain of noreturn has to end somewhere, for example throwing an exception, calling longjmp, making a system call like _exit(2), or doing something hacky with inline asm. But yes, unless you do something else that doesn't return, you should call another noreturn function (this includes exit(3) or abort(3).)
  • phonetagger
    phonetagger over 6 years
    @Ray - This back-and-forth between me and the four of you has gotten out-of-hand; I'm sorry it all appears under Andrew Henle's answer. Go back to my original comment. I simply said although Sourav's answer has a lot of good info, it didn’t (as he originally posted it, not as it appears now) technically answer the OP's question, unless you infer more from his original answer than he wrote. By contrast...
  • phonetagger
    phonetagger over 6 years
    By contrast, Andrew answered the OP's question directly, in his opening sentence. The answer isn’t “Since it’s UB it could do anything; holy cow, as random chance would have it, it returned! What luck!” The answer is, it returned because the OP’s code says to do so. Of course any answer would be remiss without mentioning the UB, which Andrew also does. But as it stands now, Sourav’s edited answer is now the better answer.