The main() Function in C: Role, Signatures, argc/argv & Exit Codes Explained
Every C program you write has one thing in common: it starts at main(). But most
beginner tutorials only show you what main() looks like, not why it
exists, what the OS actually does when it calls it, or what happens to your program after it
returns. This guide covers all of that — from the OS startup sequence to argc/argv in real
command-line tools to the exit code conventions used by every shell and CI system on the planet.
The Role of main(): Why It Exists
When the operating system runs a program, it needs a defined place to start. Different languages
solve this differently — Python executes the script file top-to-bottom, Java looks for
public static void main(String[] args), JavaScript runs from the first line.
C's solution is main() — a function with a specific name that the OS runtime
is designed to call as the first function of your program. It is not special because of any
language magic; it is special because the C runtime startup code (part of the C standard
library) is hardcoded to call a function named main after finishing its own
initialization.
This is why every C program must have exactly one main(): it is the agreed
contract between the C runtime and your code. If you don't define it, the linker reports
"undefined reference to main". If you define it twice, the linker reports "multiple definition
of main". There is exactly one entry point, no exceptions.
main()
follows all the same rules as any other C function. It can call other functions, declare local
variables, use loops and conditionals, and return a value. There is nothing syntactically
special about it beyond its name and required return type.
What Happens Before main() Runs
The path from "the OS launches your program" to "your first line of main() executes" involves several steps most developers never think about. Understanding this sequence explains some otherwise-mysterious C behaviors — like why global variables are already initialized when main() starts.
int x = 5;) are stored in the data segment with their values embedded in
the executable. By the time the first line of main() runs, all global variables are
fully initialized.
The Two Valid Signatures for main()
The C standard (C99, C11, C17, C23) defines exactly two valid forms of main().
Any other form is non-standard, even if some compilers accept it.
int main(void) or int main()Use when the program does not need command-line arguments.
void is more
explicit that no parameters are accepted; () is equally valid in C.
int main(int argc, char *argv[])Use when the program reads arguments from the command line. The parameter names
argc and argv are conventional — the compiler accepts any names.
/* Signature 1 — no command-line arguments needed */
int main(void) {
return 0;
}
/* Signature 2 — receives command-line arguments */
int main(int argc, char *argv[]) {
return 0;
}
/* Also valid — alternative argv declaration syntax */
int main(int argc, char **argv) {
return 0;
}
void main() is not standard C. Some compilers accept it as an
extension, but it violates the C standard and produces undefined behavior — the OS receives
no defined exit code. Always use int main(). GCC will warn about
void main() with -Wall.
argc and argv: Command-Line Arguments
When a user runs a C program from the terminal with arguments, those arguments are delivered
to main() through argc and argv. This is how every
command-line tool you've ever used — gcc, ls, git —
receives its options and file paths.
What argc and argv contain
argc (argument count) is the total number of strings in argv.
It is always at least 1 because argv[0] is always the program's name (or path).
argv (argument vector) is an array of char* pointers, each
pointing to a null-terminated string. argv[argc] is always a NULL pointer —
a guaranteed sentinel you can use to iterate without knowing argc.
Running: ./calc 10 + 5
#include <stdio.h>
#include <stdlib.h> // atoi()
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <num> <op> <num>\n", argv[0]);
return 1;
}
int a = atoi(argv[1]);
char op = argv[2][0];
int b = atoi(argv[3]);
int result;
switch (op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
case '*': result = a * b; break;
default:
fprintf(stderr, "Unknown operator: %c\n", op);
return 1;
}
printf("%d %c %d = %d\n", a, op, b, result);
return 0;
}
./calc 10 + 5
10 + 5 = 15
./calc 20 - 8
20 - 8 = 12
./calc
Usage: ./calc <num> <op> <num>
argv[1] when you run ./prog 42 is the string
"42", not the integer 42. Use atoi() to convert to
int. strtol() and strtod() are preferred over
atoi() because they detect invalid input and overflow.
Iterating over all arguments
/* Style 1: index-based */
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
/* Style 2: pointer-based (uses the NULL sentinel) */
for (char **arg = argv; *arg != NULL; arg++) {
printf("%s\n", *arg);
}
Return Values and Exit Codes
The integer main() returns becomes the program's exit code —
a signal sent to the OS when your program finishes. This is one of the most important
interfaces between a C program and the outside world.
| Exit code | Meaning | Real-world use |
|---|---|---|
| 0 | Success | Program completed normally. Shell scripts and CI pass. |
| 1 | General error | Most programs return 1 for unspecified failures. |
| 2 | Misuse of shell command / invalid usage | Used by bash builtins; also common for "wrong arguments" errors. |
| 126 | Command found but not executable | OS-generated; not typically returned by programs. |
| 127 | Command not found | OS-generated by shells. |
| 128+n | Fatal signal n | E.g., exit code 139 = killed by signal 11 (SIGSEGV / segfault). |
| EXIT_SUCCESS | Macro for 0 | From <stdlib.h> — readable alternative to literal 0. |
| EXIT_FAILURE | Macro for 1 | From <stdlib.h> — readable alternative to literal 1. |
# Check last program's exit code
./myprogram
echo $? # Linux/macOS: prints 0 if success, 1 if failure
# Use in a shell script condition
if ./myprogram; then
echo "Program succeeded"
else
echo "Program failed with code $?"
fi
<stdlib.h>: return EXIT_SUCCESS; and
return EXIT_FAILURE;. They communicate intent clearly and are portable.
What Happens After main() Returns
When main() returns, the C runtime performs an ordered cleanup sequence:
- atexit() handlers run in reverse registration order.
- stdio buffers are flushed — all open
FILEstreams are flushed. - Open FILE streams are closed — including stdin, stdout, and stderr.
- The exit code is passed to the OS.
#include <stdio.h>
#include <stdlib.h>
void cleanup_log() { printf("Log flushed.\n"); }
void cleanup_db() { printf("DB connection closed.\n"); }
int main() {
atexit(cleanup_log);
atexit(cleanup_db);
printf("Program running...\n");
return 0;
// Output: Program running... → DB connection closed. → Log flushed.
}
main() in Embedded Systems
In embedded systems programming, the rules around main() are slightly different.
On a bare-metal microcontroller — no operating system, no C runtime startup code — there is
no OS waiting to receive the exit code when main() returns.
For this reason, embedded main() functions almost always contain an infinite loop
— the hardware has no concept of "exit". If main() returned, the CPU would start
executing whatever is in memory after the program — random code, causing undefined behavior
or a processor fault.
int main(void) {
hardware_init();
while (1) {
read_sensors();
update_outputs();
WDT_Kick();
}
return 0; // never reached
}
int on most embedded toolchains
even though the return value is never used. Following the standard int main(void)
form keeps code portable between embedded and hosted environments.
Common main() Mistakes
void main() is not valid standard C. It produces undefined behavior and gives the OS no defined exit code.int main() or int main(void).argv[1] without checking that argc >= 2 causes a segfault if no arguments are passed."42" is not the number 42.atoi() for integers or strtol() for robust conversion.return 0; or return EXIT_SUCCESS;.main() must be defined in exactly one .c file, never in a .h file.Frequently Asked Questions
main() is the mandatory entry point of every C program — the function the
OS calls when your program starts. It must return int (the exit code) and
can optionally receive command-line arguments through argc and argv.
Every C program must have exactly one main().
return 0 sends the exit code 0 to the operating system, which by POSIX convention
means the program completed successfully. Any non-zero exit code signals failure.
Shell scripts check this with echo $?.
argc (argument count) is the number of command-line strings in argv
— always at least 1 because argv[0] is the program name. argv
(argument vector) is an array of C strings. argv[argc] is always NULL.
All values are strings — numbers must be converted with atoi() or strtol().
void main() is not valid according to the ISO C standard. The standard
requires main() to return int. GCC warns about it with -Wall.
Always use int main().
main().