The Compilation Pipeline
When you run a single command like gcc file.c -o program, it feels like one step. In reality, GCC (and every C compiler) runs your source code through four distinct stages, each with its own input, output, and dedicated tool. Understanding this pipeline demystifies compiler errors and unlocks flags you'll use for the rest of your C career.
The four stages
Stage | Input | Output | Tool |
|---|---|---|---|
Preprocessing | .c source file | Expanded source (macros resolved, headers inlined) | The preprocessor ( |
Compilation | Preprocessed C source | Assembly code ( | The compiler proper ( |
Assembly | Assembly code ( | Object file ( | The assembler ( |
Linking | One or more object files + libraries | Executable | The linker (invoked by |
Stage 1 — Preprocessing
The preprocessor runs first and handles every line beginning with #. It textually inserts the contents of #included files, expands #define macros, and resolves #if/#ifdef conditional blocks. No actual C syntax is checked at this stage — it's pure text substitution. You can see the result yourself:
gcc -E hello.c -o hello.i
Open hello.i and you'll find the full contents of stdio.h (often hundreds of lines) pasted in above your one line of code — this is why compiling even a tiny "Hello, World!" involves processing far more text than you actually typed.
Stage 2 — Compilation
The compiler proper takes the preprocessed C and translates it into assembly language for your target CPU architecture. This is where syntax errors, type mismatches, and most warnings are reported — the compiler is analyzing the meaning of your code for the first time. To stop after this stage and inspect the generated assembly:
gcc -S hello.c -o hello.s
hello.s is human-readable (if you know assembly) — it shows the exact CPU instructions your C code turned into, which is invaluable for understanding performance or debugging compiler-level issues.
Stage 3 — Assembly
The assembler converts human-readable assembly into machine code — raw bytes the CPU can execute — and packages it as an object file. Object files aren't runnable yet: they may reference functions (like printf) that live in a library and haven't been resolved.
gcc -c hello.c -o hello.o
Stage 4 — Linking
The linker takes one or more object files and combines them with the library code they depend on — for printf, that's the C standard library — into a single executable. Missing-function errors like undefined reference to 'foo' happen at this stage, not during compilation, because the linker is the component responsible for resolving those references.
gcc hello.o -o hello
./hello
Hello, World!
Running the full pipeline in one command — gcc hello.c -o hello — simply chains all four steps together automatically, using temporary files for the intermediate results instead of the named .i, .s, and .o files shown above.