C compilation — preprocessor, compiler, assembler, linker
WHY does compilation have stages?
A CPU only understands raw bytes (opcodes). C source is full of human niceties: #include, macros, comments, names like main, printf. The gap is huge, so we divide and conquer — each tool solves ONE sub-problem:
- Preprocessor → handles text substitution (
#-directives). - Compiler → translates real C into assembly for your CPU.
- Assembler → turns assembly into machine code (an object file).
- Linker → stitches object files + libraries into one executable.

Stage 1 — The Preprocessor (cpp)
WHAT it does:
#include <stdio.h>→ literally pastes the entire contents of that header file in place.#define PI 3.14→ macro: replaces everyPItoken with3.14.#ifdef / #ifndef / #endif→ conditional compilation (include code only if a macro is defined).- Strips all comments.
HOW to see it:
gcc -E hello.c # output stops after preprocessingStage 2 — The Compiler (cc1)
WHAT it produces: a .s text file of human-readable assembly mnemonics (mov, add, call).
HOW to see it:
gcc -S hello.c # produces hello.sStage 3 — The Assembler (as)
WHAT an object file contains:
- Machine code for your functions.
- A symbol table listing names defined here (e.g.
main) and names needed but not yet found (e.g.printf) — marked as undefined.
HOW to see it:
gcc -c hello.c # produces hello.o (not runnable yet!)Stage 4 — The Linker (ld)
WHAT it solves:
- Matches each undefined symbol (
printf) to its defined location inlibc. - Merges all code/data sections and assigns final memory addresses.
- Adds startup code (
_start) that callsmain.
Forecast-then-Verify
Recall Predict the output of each flag
before reading
gcc -E ::: preprocessed source (.i)
gcc -S ::: assembly (.s)
gcc -c ::: object file (.o), no linking
gcc (no flag) ::: full executable
Stopped after each? Verify by running them on a tiny file.
Common Mistakes (Steel-manned)
Mnemonic
Feynman: explain to a 12-year-old
Recall Click to read
Imagine writing a recipe to give to a robot chef who only understands button-presses.
- Preprocessor = your assistant who pastes in any borrowed recipe pages you referenced ("see the cake recipe") and copies out shorthand you defined.
- Compiler = translates your English recipe into the robot's step-by-step instruction language.
- Assembler = turns those instructions into the exact button-press codes the robot reads.
- Linker = if your recipe says "use Grandma's frosting," it goes and grabs Grandma's frosting steps and staples them in, so the robot has everything in one complete booklet it can run start to finish.
Active-Recall Flashcards
What are the 4 stages of C compilation in order?
Which stage handles #include and #define?
What does the compiler output?
.s), specific to the target CPUWhat does the assembler output and what's special about it?
.o) with machine code + a symbol table containing undefined referencesWhy can't you run a .o file directly?
printf) and no entry-point setup; the linker must resolve them firstWhich stage produces an "undefined reference" error?
Do header files contain function code?
gcc flag to stop after preprocessing?
gcc -Egcc flag to stop after compiling (get assembly)?
gcc -Sgcc flag to stop after assembling (get .o, no link)?
gcc -cWhy wrap macro parameters in parentheses?
What is relocation (linker)?
What's the file trail of the pipeline?
Connections
- C Preprocessor Directives
- Header Files and #include guards
- Static vs Dynamic Linking
- Object Files and Symbol Tables
- Makefiles and Incremental Builds
- Assembly Language Basics
- Compiler Optimization Levels (-O0..-O3)
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, jab tum gcc hello.c -o hello chalate ho, toh lagta hai ek hi step me kaam ho gaya. Lekin asal me andar 4 tools ek line me kaam karte hain — ek pipeline. Har tool tumhare code ko thoda aur "machine-jaisa" banata hai. Pehle Preprocessor aata hai: yeh #include aur #define jaise # waale lines ko handle karta hai — bilkul copy-paste (text substitution) ki tarah, koi type-checking nahi.
Uske baad Compiler asli kaam karta hai: tumhari C ko samajhta hai, type check karta hai, aur usse assembly language (.s) me badal deta hai jo tumhare CPU ke hisaab se hoti hai. Phir Assembler us assembly ko machine code me convert karke ek object file (.o) banata hai. Yeh .o abhi run nahi hoti, kyunki printf jaise functions ka actual code ismein nahi hota — sirf ek "hole" hota hai jisme likha hai "mujhe printf chahiye".
Aakhri me Linker aata hai — yeh saari .o files aur libraries (jaise libc jisme printf ka code hai) ko jod kar saare holes bharta hai, addresses fix karta hai (relocation), aur ek final executable banata hai jo chal sakti hai. Isiliye undefined reference to 'foo' error linker deta hai, compiler nahi — yeh batata hai ki definition missing hai, syntax galat nahi hai.
Yaad rakhne ka fayda: agar error compiler se aaya toh syntax/type galat hai; agar linker se aaya toh koi function/library missing hai. Aur header files me sirf declarations hoti hain (promise), actual code library me hota hai (delivery) — yeh confusion bahut students ko hoti hai. Pipeline yaad rakho: P-C-A-L, aur files: .c → .i → .s → .o → exe.