Why does "noreturn" function return?
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 wheref
was previously declared with thenoreturn
attribute andf
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:
- Be "nice" to you and figure out a way to have the function return properly anyway.
- Emit code that, when the function improperly returns, it crashes or behaves in arbitrarily unpredictable ways.
- 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
.
msc
Hey, I am Mahendra. I am passionate about programming. I am working in C, C++ and embedded. Thank you.
Updated on June 14, 2022Comments
-
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 providingnoreturn
attribute? -
kocica over 6 yearsThats true but you should read this
-
ForceBru over 6 years@FilipKočica, this is exactly what I was reading when you posted this comment
-
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 ofnoreturn
actually placing restriction on what the code he wrote is allowed to do under the standard. -
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, ornonnull
which is a promise that you won't pass a null pointer to the function so the compiler (allowing it to remove anynull
checks you might do inside the function). -
cmaster - reinstate monica over 6 yearsActually, 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___________ over 6 years@cmaster no attributes make code sane, only the knowledge of the coder.
-
cmaster - reinstate monica over 6 yearsThat'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 ofnoreturn
makes code more or less sane. -
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 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 over 6 yearsThough 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 over 6 yearsI 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 containsnoreturn
calls in the performance critical path (by means oflongjmp()
). 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 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 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 providingnoreturn
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 mynoreturn
function crash the program when it returns?” -
Hong Ooi over 6 yearstl;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 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 typeNothing
for a function that doesn't return. -
Theodoros Chatzigiannakis over 6 years@MartinJames Look into bottom types for more information.
-
user2357112 over 6 years"Should" and "encouraged" are not requirements. "Must" is a requirement. Compilers are not required to warn for this.
-
Random832 over 6 yearsWhy does func emit a
ret
instruction (instead ofud2
or nothing)? -
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 eachcall
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 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 over 6 yearsAbout 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 over 6 years@Random823 That would indeed be the most beneficial thing, along with a warning during compilation.
-
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 over 6 yearsAm 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 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 over 6 yearsI 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 thatnoreturn
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 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 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 over 6 yearsIn other words, don't lie to the compiler.
-
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 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 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 over 6 yearsHuh, 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 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 over 6 yearsHere'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 over 6 yearsOops, 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 over 6 yearsAh, it's because gcc's
noreturn
handling specifically disables tailcall optimization for betterabort()
backtraces: gcc.gnu.org/bugzilla/show_bug.cgi?id=55747#c4. -
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 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 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 optimizingnoreturn
functions. And that's in kernel space. In user space, where most code is deployed, there is even less optimization potential. -
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'tnoreturn
after all. -
Peter Cordes over 6 yearsSo 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 over 6 yearsThe chain of
noreturn
has to end somewhere, for example throwing an exception, callinglongjmp
, 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 anothernoreturn
function (this includesexit(3)
orabort(3)
.) -
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 over 6 yearsBy 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.