Why does "noreturn" function return?

CAssemblyC11Function CallNoreturn

C Problem Overview


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?

C Solutions


Solution 1 - C

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 - C

> 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][1] 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. [1]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

Solution 3 - C

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 - C

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 - C

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.

Solution 6 - C

According to this

> If the function declared _Noreturn returns, the behavior is undefined. A compiler diagnostic is recommended if this can be detected.

It is the programmer's responsibility to make sure that this function never returns, e.g. exit(1) at the end of the function.

Solution 7 - C

ret simply means that the function returns control back to the caller. So, main does call func, the CPU executes the function, and then, with ret, the CPU continues execution of main.

Edit

So, it turns out, noreturn does not make the function not return at all, it's just a specifier that tells the compiler that the code of this function is written in such a way that the function won't return. So, what you should do here is to make sure that this function actually doesn't return control back to the callee. For example, you could call exit inside it.

Also, given what I've read about this specifier it seems that in order to make sure the function won't return to its point of invocation, one should call another noreturn function inside it and make sure that the latter is always run (in order to avoid undefined behavior) and doesn't cause UB itself.

Solution 8 - C

no return function does not save the registers on the entry as it is not necessary. It makes the optimisations easier. Great for the scheduler routine for example.

See the example here: https://godbolt.org/g/2N3THC and spot the difference

Solution 9 - C

TL:DR: It's a missed-optimization by gcc.


noreturn is a promise to the compiler that the function won't return. This allows optimizations, and is useful especially in cases where it's hard for the compiler to prove that a loop won't ever exit, or otherwise prove there's no path through a function that returns.

GCC already optimizes main to fall off the end of the function if func() returns, even with the default -O0 (minimum optimization level) that it looks like you used.

The output for func() itself could be considered a missed optimization; it could just omit everything after the function call (since having the call not return is the only way the function itself can be noreturn). It's not a great example since printf is a standard C function that is known to return normally (unless you setvbuf to give stdout a buffer that will segfault?)

Lets use a different function that the compiler doesn't know about.

void ext(void);

//static
int foo;

_Noreturn void func(int *p, int a) {
    ext();
    *p = a;     // using function args after a function call
    foo = 1;    // requires save/restore of registers
}

void bar() {
        func(&foo, 3);
}

(Code + x86-64 asm on the Godbolt compiler explorer.)

gcc7.2 output for bar() is interesting. It inlines func(), and eliminates the foo=3 dead store, leaving just:

bar:
    sub     rsp, 8    ## align the stack
    call    ext
    mov     DWORD PTR foo[rip], 1
   ## fall off the end

Gcc still assumes that ext() is going to return, otherwise it could have just tail-called ext() with jmp ext. But gcc doesn't tailcall noreturn functions, because that loses backtrace info for things like abort(). Apparently inlining them is ok, though.

Gcc could have optimized by omitting the mov store after the call as well. If ext returns, the program is hosed, so there's no point generating any of that code. Clang does make that optimization in bar() / main().


func itself is more interesting, and a bigger missed optimization.

gcc and clang both emit nearly the same thing:

func:
    push    rbp            # save some call-preserved regs
    push    rbx
    mov     ebp, esi       # save function args for after ext()
    mov     rbx, rdi
    sub     rsp, 8          # align the stack before a call
    call    ext
    mov     DWORD PTR [rbx], ebp     #  *p = a;
    mov     DWORD PTR foo[rip], 1    #  foo = 1
    add     rsp, 8
    pop     rbx            # restore call-preserved regs
    pop     rbp
    ret

This function could assume that it doesn't return, and use rbx and rbp without saving/restoring them.

Gcc for ARM32 actually does that, but still emits instructions to return otherwise cleanly. So a noreturn function that does actually return on ARM32 will break the ABI and cause hard-to-debug problems in the caller or later. (Undefined behaviour allows this, but it's at least a quality-of-implementation problem: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82158.)

This is a useful optimization in cases where gcc can't prove whether a function does or doesn't return. (It's obviously harmful when the function does simply return, though. Gcc warns when it's sure a noreturn function does return.) Other gcc target architectures don't do this; that's also a missed optimization.

But gcc doesn't go far enough: optimizing away the return instruction as well (or replacing it with an illegal instruction) would save code size and guarantee noisy failure instead of silent corruption.

And if you're going to optimize away the ret, optimizing away everything that's only needed if the function will return makes sense.

Thus, func() could be compiled to:

    sub     rsp, 8
    call    ext
    # *p = a;  and so on assumed to never happen
    ud2                 # optional: illegal insn instead of fall-through

Every other instruction present is a missed optimization. If ext is declared noreturn, that's exactly what we get.

Any basic block that ends with a return could be assumed to never be reached.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmscView Question on Stackoverflow
Solution 1 - CSourav GhoshView Answer on Stackoverflow
Solution 2 - CAndrew HenleView Answer on Stackoverflow
Solution 3 - CSteve SummitView Answer on Stackoverflow
Solution 4 - CGrooView Answer on Stackoverflow
Solution 5 - CnneonneoView Answer on Stackoverflow
Solution 6 - CChrisBView Answer on Stackoverflow
Solution 7 - CForceBruView Answer on Stackoverflow
Solution 8 - C0___________View Answer on Stackoverflow
Solution 9 - CPeter CordesView Answer on Stackoverflow