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.
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:
#include <stdlib.h>
int main(void) {
if (/* something went wrong */ 0) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}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.
#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 |
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.
#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 */
}$ ./app; echo "exit code: $?"
Doing work... Cleaning up before exit... exit code: 0
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.