The C Path — learn C, visually

🚀 Modern C (C11 → C23)

_Noreturn: functions that never come back

⏱ 9 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

If 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.

why.c — the warning noreturn fixes
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 */
terminal
$ 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:

die.c
#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;
}
terminal
$ 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)

⚠️

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)