Last updated: March 2025

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.

Advertisement

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() is just a function. Apart from being called first, 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.
Explore Now → Learn More

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.

OS loads the executableThe OS reads the ELF/PE/Mach-O binary from disk into memory, sets up the process's virtual address space, and maps code, data, and BSS segments.
OS
C runtime startup (crt0/crt1)The first code to run is the C runtime startup code. It initializes the stack pointer, sets up the heap allocator, zeroes the BSS segment (uninitialized globals), initializes explicitly initialized global variables, and sets up stdin/stdout/stderr.
C runtime
Environment and arguments preparedargc, argv[], and envp[] (environment variables) are assembled from the OS-provided data and passed as parameters to main().
C runtime
main() is calledThe C runtime calls main(argc, argv). Your code runs from here.
Your code
main() returnsThe C runtime receives your return value, flushes stdio buffers, calls atexit() handlers in reverse order, and runs any cleanup registered with the runtime.
C runtime
OS cleans up the processThe OS receives the exit code, reclaims all memory, closes file descriptors, and marks the process as terminated.
OS
Why global variables are initialized before main(): The C runtime's job includes initializing all global and static variables before calling main(). Zero-initialized globals go into the BSS segment (the runtime zeroes it). Explicitly initialized globals (like 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.
Advertisement

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.

Signature 1 — no arguments
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.
Signature 2 — command-line arguments
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.
both valid signaturesC
/* 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

argc
4
argv[0]
./calc← program name
argv[1]
"10"← first argument (it's a string, not an int!)
argv[2]
"+"
argv[3]
"5"
argv[4]
NULL← always NULL sentinel
real-world argc/argv usage — a simple calculator CLIC
#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;
}
terminalShell
./calc 10 + 5
10 + 5 = 15

./calc 20 - 8
20 - 8 = 12

./calc
Usage: ./calc <num> <op> <num>
All argv values are strings. Even numbers passed on the command line arrive as strings — 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

iterate over argv using both stylesC
/* 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);
}
Advertisement

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 codeMeaningReal-world use
0SuccessProgram completed normally. Shell scripts and CI pass.
1General errorMost programs return 1 for unspecified failures.
2Misuse of shell command / invalid usageUsed by bash builtins; also common for "wrong arguments" errors.
126Command found but not executableOS-generated; not typically returned by programs.
127Command not foundOS-generated by shells.
128+nFatal signal nE.g., exit code 139 = killed by signal 11 (SIGSEGV / segfault).
EXIT_SUCCESSMacro for 0From <stdlib.h> — readable alternative to literal 0.
EXIT_FAILUREMacro for 1From <stdlib.h> — readable alternative to literal 1.
checking exit codes in shell scripts and CIShell
# 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
Use EXIT_SUCCESS and EXIT_FAILURE. Instead of magic numbers, use the macros from <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:

  1. atexit() handlers run in reverse registration order.
  2. stdio buffers are flushed — all open FILE streams are flushed.
  3. Open FILE streams are closed — including stdin, stdout, and stderr.
  4. The exit code is passed to the OS.
atexit() — register cleanup functionsC
#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.
}
Advertisement

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.

main() in embedded C — bare metal microcontrollerEmbedded C
int main(void) {
    hardware_init();

    while (1) {
        read_sensors();
        update_outputs();
        WDT_Kick();
    }

    return 0;  // never reached
}
The return type is still 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

Using void main() instead of int main()
void main() is not valid standard C. It produces undefined behavior and gives the OS no defined exit code.
✅ Fix: Always use int main() or int main(void).
Forgetting to check argc before accessing argv
Accessing argv[1] without checking that argc >= 2 causes a segfault if no arguments are passed.
✅ Fix: Always validate argc before reading argv elements.
Treating argv values as numbers directly
argv arguments are always strings. The string "42" is not the number 42.
✅ Fix: Convert with atoi() for integers or strtol() for robust conversion.
Omitting return 0 and expecting it to be implicit
In C99 and later, falling off the end of main() implicitly returns 0, but this is unclear and flagged by static analysis tools.
✅ Fix: Always explicitly write return 0; or return EXIT_SUCCESS;.
Defining main() in a header file
Including the header in two .c files gives the linker two definitions of main — "multiple definition" error.
✅ Fix: main() must be defined in exactly one .c file, never in a .h file.
Advertisement

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().
No. 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().
The C runtime (crt0/crt1) sets up the stack, zeroes uninitialized globals, initializes explicit global variables, sets up stdio, and assembles argc/argv before calling main().
No. The linker reports "multiple definition of main" if two .c files both define it. Every C program has exactly one main().
Advertisement

Continue learning C on CoodeVerse

CoodeVerse Editorial Team

The CoodeVerse editorial team consists of experienced software developers and educators specializing in C, Python, Java, and web development. All content is technically reviewed and updated regularly.