🚀 Modern C (C11 → C23)
_Noreturn: functions that never come back
▶ Open the interactive lesson — free, no signupIf you've ever written a helper that prints an error message and quits, you've probably also met gcc's baffling complaint about it — "control reaches end of non-void function" — on code that was perfectly fine. One word tells the compiler the truth: this function never comes back. The false alarm vanishes, and the compiler still catches real missing-return bugs.
Some functions don't return. Not "return nothing" like void — they never hand control back at all: exit() ends the process, abort() kills it, longjmp() jumps execution back to an earlier point in the program, thrd_exit() ends the thread. C11 gave us a way to tell the compiler this: the _Noreturn keyword, written in front of a function (with a pretty noreturn macro in <stdnoreturn.h>).
Why the compiler cares
Two reasons. Better warnings: if the compiler doesn't know die() never returns, it thinks the code after it is reachable — and warns about "control reaches end of non-void function" in perfectly fine code, or worse, fails to warn about genuinely missing returns. Better code: a call that never returns needs no "afterwards" — the compiler can skip saving registers and delete everything downstream of the call.
void die_unmarked(const char *msg); /* compiler assumes it returns */
int parse_level(const char *s) {
if (s[0] == 'a') return 1;
if (s[0] == 'b') return 2;
die_unmarked("bad level");
} /* warning: control reaches end of non-void function
— a FALSE alarm, but the compiler can't know that */$ gcc -std=c17 -Wall why.c -c why.c: In function 'parse_level': why.c:7:1: warning: control reaches end of non-void function [-Wreturn-type] # mark die_unmarked as noreturn and this warning vanishes — # and REAL missing-return bugs still get caught
🧠 Checkpoint: What does _Noreturn promise the compiler?
- The function returns void
- The function has no side effects
- Control never comes back to the caller
- The function never fails
Show answer
Control never comes back to the caller — It’s purely about control flow: after the call, execution never resumes at the call site — because of exit(), abort(), longjmp(), an infinite loop, etc. "Returns no value" is void; noreturn is a much stronger claim.
The die() pattern
Nearly every serious C codebase has a fatal-error helper: print a message, exit with failure. Marking it noreturn is what makes it compose cleanly with the rest of your code:
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h> /* the 'noreturn' macro (C11/C17) */
noreturn void die(const char *msg) {
fprintf(stderr, "fatal: %s\n", msg);
exit(EXIT_FAILURE); /* exit is itself noreturn */
}
int parse_level(const char *s) {
if (s[0] == 'a') return 1;
if (s[0] == 'b') return 2;
die("bad level string"); /* no return needed after this */
} /* ...and no warning: dead end. */
int main(void) {
printf("level a = %d\n", parse_level("a"));
printf("level x = %d\n", parse_level("x")); /* boom */
printf("never printed\n");
return 0;
}$ gcc -std=c17 -Wall die.c -o die && ./die level a = 1 fatal: bad level string $ echo $? 1 # exit status 1 — scripts and Makefiles can see the failure
Look at parse_level: no return after the die() call, and no warning either — the compiler knows that path is a dead end. Delete the noreturn and gcc immediately complains that control can reach the end of a non-void function. The flow makes it obvious:
▶ This spot has an interactive flow widget — open the interactive lesson to play with it.
🧠 Checkpoint: A noreturn function executes a plain return;. What happens?
- A compile error, always
- It works like a normal void return
- Undefined behavior
- The program exits with status 0
Show answer
Undefined behavior — The standard says behavior is undefined if a noreturn function actually returns. Compilers try to warn when they can see it, but the promise is yours to keep — the optimizer may have deleted the caller’s "afterwards" entirely.
The rules (and the C23 facelift)
- A noreturn function should have return type
void— a value it can never produce would be nonsense. - If a noreturn function does return (falls off the end, or hits a
return;), behavior is undefined. The promise cuts both ways. - C23 deprecates
_Noreturnand<stdnoreturn.h>in favor of the attribute syntax:[[noreturn]] void die(const char *msg);— same meaning, modern spelling (more on attributes in the C23 lesson).
Don't confuse void with noreturn. A void function returns — it just carries no value; execution continues at the caller. A noreturn function never resumes the caller at all. And never mark main noreturn: returning from main is its normal job.
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
🧠 Checkpoint: Which spelling does C23 prefer?
[[noreturn]] void die(…);__noreturn__ void die(…);void noreturn die(…);_Noreturn— unchanged
Show answer
[[noreturn]] void die(…); — C23 adopts the [[attribute]] syntax and deprecates both _Noreturn and stdnoreturn.h. Old spellings still compile (deprecated ≠ removed), but new code should write [[noreturn]].
Next: a corner of C most people never visit — built-in complex numbers, where C quietly beats your calculator.
▶ Practice this lesson interactively (with live gcc)