CExit Codes & exit()

Exit Codes & exit()

When a C program finishes, it hands a small integer back to whatever launched it — the shell, a parent process, a CI pipeline. This is the exit code (also called the exit status), and by convention it tells the caller whether the program succeeded or failed. Scripts and tools rely on this constantly: a shell chain like cmd1 && cmd2 only runs cmd2 if cmd1's exit code was 0.

The 0-Means-Success Convention
  • An exit code of 0 conventionally means the program completed successfully.

  • Any non-zero exit code conventionally means some kind of error occurred.

  • Different non-zero values can be used to distinguish different kinds of failure.

C
int main(void) {
    /* returning 0 from main signals success */
    return 0;
}
EXIT_SUCCESS and EXIT_FAILURE

Rather than hardcoding 0 and 1, the standard header stdlib.h defines two macros that express intent more clearly and are the portable way to signal success or failure:

C
#include <stdlib.h>

int main(void) {
    if (/* something went wrong */ 0) {
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
Note
EXIT_SUCCESS is guaranteed by the C standard to indicate successful termination, and EXIT_FAILURE to indicate unsuccessful termination — but the standard does not require their underlying numeric values to be exactly 0 and 1 on every platform. In practice EXIT_SUCCESS is almost always 0 and EXIT_FAILURE is almost always 1, but using the macros instead of the literals is more portable and communicates intent to readers.
exit() vs return from main

You can end a program in two ways: returning a value from main, or calling the exit() function (declared in stdlib.h) from anywhere in the program, including deep inside a helper function called far from main.

C
#include <stdio.h>
#include <stdlib.h>

void fail_fast(void) {
    fprintf(stderr, "Fatal error, terminating.\n");
    exit(EXIT_FAILURE); /* ends the whole program immediately */
}

int main(void) {
    printf("Before\n");
    fail_fast();
    printf("This line never runs\n");
    return EXIT_SUCCESS;
}

Mechanism

Where it can be used

Effect

return code;

Only inside main

Ends main; equivalent to calling exit(code) implicitly

exit(code);

Anywhere in the program

Terminates the whole process immediately, from any function

Note
C has no destructors (that is a C++ feature), so there is no "stack unwinding running cleanup code" concern the way there is in C++. However, the exit() vs return distinction still matters for one thing: functions registered with atexit() only run when the program terminates via exit() (or via return from main, since that implicitly calls exit()) — they do NOT run if the process is killed abruptly by a signal or by _exit()/abort().
Registering Cleanup with atexit()

atexit() lets you register a function to run automatically when the program terminates normally, no matter whether termination happens via return from main or an exit() call somewhere else. This is useful for cleanup like flushing logs or releasing global resources.

C
#include <stdio.h>
#include <stdlib.h>

void cleanup(void) {
    printf("Cleaning up before exit...\n");
}

int main(void) {
    atexit(cleanup);

    printf("Doing work...\n");
    exit(EXIT_SUCCESS); /* cleanup() still runs before the process actually ends */
}
Example

Bash
$ ./app; echo "exit code: $?"
Doing work...
Cleaning up before exit...
exit code: 0
Tip
You can register multiple functions with atexit(); they run in the reverse order of registration (last registered, first run) — similar in spirit to how a stack unwinds.
Warning
exit() only runs atexit handlers and standard cleanup like flushing stdio buffers. It does not close resources you manage yourself (like open file descriptors from lower-level APIs) unless you clean those up yourself or the operating system reclaims them on process termination.
Summary
  • 0 conventionally means success; non-zero means some kind of failure.

  • Prefer EXIT_SUCCESS/EXIT_FAILURE from stdlib.h over bare 0/1 literals.

  • return from main and exit() both terminate the program; exit() can be called from any function.

  • atexit() registers cleanup functions that run on normal termination, in reverse registration order.