Every C programmer has written this program. It does nothing useful. It is six lines — seven if you count the blank one. But it is the first time the toolchain runs end to end, the first time you write something the machine actually executes. Write it, compile it, run it.
#include <stdio.h>
int main(void)
{
printf("Hello, world.\n");
return (0);
}gcc hello.c -o hello
./helloHello, world.The rest of this page explains what just happened.
The compilation pipeline
Running gcc hello.c -o hello is not one step — it is four, run
in sequence by the compiler driver:
You can stop at each stage and inspect the output:
gcc -E hello.c -o hello.i # preprocessor output
gcc -S hello.c -o hello.s # assembly
gcc -c hello.c -o hello.o # object file
gcc hello.o -o hello # linker: gcc calls ld with CRT startup filled inThe last line uses gcc, not ld. When you hand gcc an object
file, it skips compilation and goes straight to linking — filling in
the C runtime paths and library flags for you. Those paths are
platform-specific and easy to get wrong, so you do not need to
memorise them. In day-to-day work you will just pass .c files to
gcc and let it run all four stages at once.
It is still worth stopping at each stage once, to see what the
toolchain actually does on your behalf. Open hello.i. Near the
bottom you will find your three lines of code, preceded by several
hundred lines of declarations pulled in from stdio.h. That is what
#include <stdio.h> actually does: it copies a header file into your
source before the compiler sees it.
You do not need to understand all of that output now. The point is that compilation is not magic — it is a sequence of text transformations from source to executable, and you can inspect any intermediate step.
Anatomy of hello.c
#include <stdio.h> — a preprocessor directive. Tells the
preprocessor to insert the contents of stdio.h (the standard I/O
header) here. Without it, the compiler does not know what printf is.
int main(void) — every C program starts here. The operating
system calls main when it launches your program. int is the return
type — main returns an integer to the shell. void means it takes
no arguments (when you have no interest in command-line arguments).
printf("Hello, world.\n") — a function call. printf is declared
in stdio.h and defined in the C standard library, which the linker
pulls in automatically. \n is a newline character — without it, the
shell prompt would appear on the same line as your output.
return (0) — returns 0 to the shell. By convention, 0 means
success. Any other value signals an error. The shell can inspect this
with echo $? immediately after running your program.
Reading compiler errors
Remove the semicolon from the printf line and compile again:
printf("Hello, world.\n")
return (0);gcc hello.c -o hellohello.c:5:5: error: expected ';' before 'return'
5 | return (0);
| ^~~~~~The compiler says the error is on line 5 (return), but the missing
semicolon is on line 4 (printf). The compiler reports where it gave
up, not always where the mistake is. When a line looks correct, check
the line above.
Add the semicolon back. Compile again. The error disappears.
This error-read-fix loop is the core skill of working with a compiler. You will repeat it thousands of times. It gets faster.
Flags worth using now
Add -Wall -Wextra to every compile command from here on:
gcc -Wall -Wextra hello.c -o hello-Wall enables most common warnings. -Wextra enables a few more.
A warning is not an error — the program still compiles — but warnings
are the compiler telling you that something looks wrong. Treat them as
errors: fix every warning before moving on.
The tester compiles your programs with -Wall -Wextra. If your program
compiles cleanly without those flags but produces warnings with them,
the tester will flag it.