5.3.1Build Systems & Toolchain

Compilation stages — preprocessing, compilation, assembly, linking

1,923 words9 min readdifficulty · medium
Figure — Compilation stages — preprocessing, compilation, assembly, linking

WHY does this pipeline exist?


Stage 1 — Preprocessing

WHAT it does:

  • #include <stdio.h> → physically pastes the entire contents of that header file.
  • #define PI 3.14 → replaces every PI token with 3.14.
  • #ifdef / #ifndef / #endif → keeps or deletes blocks of code conditionally.
  • Strips comments.

HOW to see it:

gcc -E hello.c -o hello.i   # -E = stop after preprocessing

Stage 2 — Compilation

WHAT happens inside:

  1. Lexing → break text into tokens.
  2. Parsing → build an Abstract Syntax Tree (AST).
  3. Semantic analysis → type checks (this is where most of your errors come from!).
  4. Optimization → e.g. constant folding 2*36.
  5. Code generation → emit assembly for the target CPU (x86, ARM…).

HOW to see it:

gcc -S hello.i -o hello.s   # -S = stop after compilation

Stage 3 — Assembly

WHY not runnable yet? Because it may call functions (like printf) that live in other files. Their addresses are still unknown placeholders.

HOW to see it:

gcc -c hello.s -o hello.o   # -c = compile+assemble, stop before linking

Stage 4 — Linking

WHAT it resolves:

  • Your hello.o says "I need printf". The linker finds printf inside the C standard library (libc) and patches the address in.

Two kinds:

  • Static linking → library code is copied into the executable (bigger file, self-contained).
  • Dynamic linking → only a reference is stored; the actual .so/.dll is loaded at runtime (smaller file, shared).

HOW:

gcc hello.o -o hello        # links, produces executable

Common Mistakes (Steel-manned)


Forecast-then-Verify


80/20 — The vital few



Recall Feynman: explain to a 12-year-old

Imagine you wrote a recipe in English. First, a helper copies in all the sub-recipes you referenced and fixes up shorthand notes (preprocess). Then a translator rewrites it into the chef's special cooking-shorthand (compile). A second person turns that shorthand into exact robot-arm button presses (assemble) — but the recipe still says "use sauce from the other kitchen". Finally, the linker runs around collecting all the sauces from other kitchens and glues everything into ONE finished cookbook the robot can run start-to-finish.


Connections


Flashcards

What are the 4 compilation stages in order?
Preprocessing → Compilation → Assembly → Linking
Which gcc flag stops after preprocessing?
-E (produces .i)
Which gcc flag stops after compilation (emits assembly)?
-S (produces .s)
Which gcc flag compiles+assembles but does not link?
-c (produces .o)
What does the preprocessor actually do to #include?
Physically pastes the entire header file's text into the source.
What is a translation unit?
The pure-C output of the preprocessor (no # directives left), the actual input to the compiler.
Why is SQ(x) ((x)*(x)) parenthesized?
Macros are text substitution; without parens, operator precedence breaks expressions like SQ(3+1).
What does the assembler produce and why isn't it runnable?
An object file (.o) with machine code, but it has unresolved symbols and placeholder addresses.
What is the linker's main job?
Combine object files + libraries, resolve symbols, and relocate addresses into one executable.
At which stage does undefined reference to 'x' occur?
Linking — a symbol is used but never defined.
Static vs dynamic linking?
Static copies library code into the executable; dynamic stores a reference loaded at runtime from a shared library.
Where do type-checking errors occur?
During the compilation stage (semantic analysis).
What lives in an object file's symbol table?
Symbols it defines and symbols it needs (undefined references).
File extension chain for a C program?
.c → .i → .s → .o → executable

Concept Map

input to

produces .i

input to

produces .s

input to

produces .o

stitched by

plus libraries

handles

does

combines many

C source .c

Preprocessor

Translation unit

Compiler

Assembly

Assembler

Object bytes

Linker

Executable

Hash directives + macros

Lex parse type-check optimize

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, jab tum gcc hello.c -o hello chalाते ho, lagta hai ek hi step hai — par andar 4 stages chalte hain, ek factory line ki tarah. Pehla stage hai preprocessing: yeh sirf text ka kaam karta hai. Jitne bhi # waale lines hain (#include, #define), unhe handle karta hai — header file ka pura content paste kar deta hai, aur macros ko replace kar deta hai. Yeh math nahi samajhta, sirf text copy-paste karta hai, isliye macros mein parentheses zaroori hain.

Dusra stage compilation hai — yahan asli C code ko assembly language mein convert karta hai. Yahi par type-checking aur syntax errors pakde jaate hain, aur optimization hoti hai. Output .s file hota hai jo CPU-specific hota hai (x86, ARM). Teesra assembly stage assembly ko raw machine code (.o object file) banata hai — par yeh abhi run nahi ho sakta, kyunki printf jaise functions ke address abhi unknown hain.

Aakhri stage linking hai. Yeh sab object files aur libraries ko jod kar ek executable banata hai, aur saare "I need printf" jaise symbols ko resolve karta hai. Agar koi function use hua par kahin defined nahi mila, to undefined reference error aata hai — yaad rakho yeh link-time error hai, compile error nahi! Tumhara syntax sahi tha, bas definition ya library missing thi.

Exam aur debugging dono ke liye yeh samajhna critical hai: agar error expected ';' jaisa hai to compile stage, agar undefined reference hai to link stage, aur agar #include file nahi mili to preprocess stage. Isse tum turant pata laga loge ki problem kahan hai — yahi 80/20 fayda hai.

Go deeper — visual, from zero

Test yourself — Build Systems & Toolchain

Connections