Foundations: from source to running program · about 35 min

Your first program: main and exit status

Why this matters

Every C program you write begins the same way, and every program you run reports back a number when it finishes. That number is how scripts, build systems and other programs know whether your program succeeded. make stops a build when a compiler exits with a nonzero status; a shell script using set -e aborts; CI systems mark a job red. A program that prints "error" but exits with 0 is lying to all of them.

The smallest complete program

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -g -O0 -o prog main.c
$ ./prog
Hello, world!

Line by line:

C does not care about indentation or line breaks; they are for humans. This is equally valid and equally horrible:

#include <stdio.h>
int main(void){printf("Hello, world!\n");return 0;}

The #include line is the exception: preprocessor directives must be on their own line.

Who calls main?

You never call main yourself. When the kernel starts your program, it jumps to a small piece of startup code from the C library (on Linux, a function usually called _start). That code sets up the environment, gathers command-line arguments, and then calls your main. When main returns, the startup code takes the return value and passes it to exit, which flushes output buffers, closes files, and asks the kernel to end the process with that status.

  kernel starts process
        |
        v
  _start (C library)
        |  calls
        v
  main()  <-- your code
        |  returns 0
        v
  exit(0)  flush output, tell kernel "done, status 0"
        |
        v
  shell receives status 0  ($? == 0)

Exit status

In the shell, $? holds the exit status of the last command:

$ ./prog
Hello, world!
$ echo $?
0

Change the program to return 7;, rebuild, and:

$ ./prog
Hello, world!
$ echo $?
7

Conventions:

<stdlib.h> defines two portable names, EXIT_SUCCESS (0) and EXIT_FAILURE (1 on Linux). They make intent obvious to a reader.

Shells use the status directly:

$ ./prog && echo "it worked"
Hello, world!
it worked

&& runs the second command only if the first exited with 0.

Ending the program from anywhere: exit()

return leaves the current function. In main that ends the program, but inside another function it only goes back to the caller. To end the program from deep inside, call exit:

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

void fail(void)
{
    printf("fatal: giving up\n");
    exit(EXIT_FAILURE);      /* never returns */
}

int main(void)
{
    printf("step 1\n");
    fail();
    printf("step 2\n");      /* never reached */
    return 0;
}
$ ./prog
step 1
fatal: giving up
$ echo $?
1

exit runs the same cleanup that returning from main does: it flushes buffered output and closes open files. (There is also _exit, which skips that cleanup; you will meet it in the systems module.)

Functions in one paragraph

You have now defined two functions, main and fail. A function definition has a return type, a name, a parameter list in parentheses, and a body in braces. void as a return type means "returns nothing"; void in the parentheses means "takes nothing". A function must be declared before it is called, which is why fail is written above main (or declared above it with a prototype, as you saw in the previous lesson). Functions get a whole module later; for now this is enough to read the examples.

Common mistakes

Recap

Going further: run man 3 exit in the sandbox terminal and read the description. Then try ./prog; echo $? with a few different return values, including 256 and -1.

Exercises

Exercise 1 · write code

Report a status

Write a program that prints exactly

Something went slightly wrong

and then ends with exit status 3.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    printf("Something went slightly wrong\n");
    return 0;
}

Example

expected output

Something went slightly wrong
Hint 1

The value after return in main becomes the exit status.

Reference solution
#include <stdio.h>

int main(void)
{
    printf("Something went slightly wrong\n");
    return 3;
}

Exercise 2 · quiz

Exit status range

On Linux, a program's main executes return 258;. What does echo $? print in the shell right afterwards?

Answer
  • 2

Only the low 8 bits of the status reach the parent process, so the value is taken modulo 256: 258 - 256 = 2. Exit statuses are therefore 0 to 255. By convention 0 means success and any other value means some kind of failure; many tools document what each number means (for example grep exits 1 for "no match" and 2 for "error").

Exercise 3 · write code

Leaving early with exit()

The starter calls a function check_config that is supposed to stop the whole program when the configuration is bad. Right now it only prints a message and returns, so main carries on.

Change check_config so that, after printing its message, it ends the program immediately with the status EXIT_FAILURE (use the exit function from <stdlib.h>). The program must print exactly this one line, and nothing else, and exit with status 1:

config: missing value

File: main.c

Starting point

#include <stdio.h>

void check_config(void)
{
    printf("config: missing value\n");
}

int main(void)
{
    check_config();
    printf("Starting server\n");
    return 0;
}

Example

expected output

config: missing value
Hint 1

return inside check_config only leaves check_config. exit(status) ends the whole program.

Hint 2

Add #include <stdlib.h> and call exit(EXIT_FAILURE); after the printf.

Reference solution
#include <stdio.h>
#include <stdlib.h>

void check_config(void)
{
    printf("config: missing value\n");
    exit(EXIT_FAILURE);
}

int main(void)
{
    check_config();
    printf("Starting server\n");
    return 0;
}

Exercise 4 · quiz

What main returns

Which statements about int main(void) are true? Select all that apply.

Answer
  • The int says main returns an integer, which becomes the exit status

  • (void) means main takes no arguments

  • If main reaches its closing brace without a return statement, the exit status is 0

Since C99, falling off the end of main is defined to return 0 (this is a special rule for main only; any other int function that falls off its end returns garbage). Every program has exactly one main: it is the name the C startup code calls. Writing an explicit return 0; is still good style because it states the intent.