🧮 Foundations — Before C
From source to silicon: the compiler pipeline
▶ Open the interactive lesson — free, no signupSooner or later a program of yours will refuse to build, and the error might come from any of four different tools — each speaking its own vocabulary. Once you know the four-step journey from the text you type to something your computer can actually run, every build error instantly tells you where to look. You'll lean on this map for every C program you ever write.
You write hello.c — a plain text file. The CPU executes raw machine code — numeric instructions, no text in sight. What happens in between? When you run gcc hello.c (GCC is the standard C compiler on Linux), your code takes a four-stage journey:
▶ This spot has an interactive flow widget — open the interactive lesson to play with it.
Stage by stage
1. Preprocessing (cpp)
A pure text transformation: #include lines are replaced by the entire contents of header files, macros are expanded, #if blocks are kept or deleted. No C is understood yet — it's find-and-replace on steroids. See it yourself with gcc -E hello.c (brace yourself: stdio.h expands to ~800 lines).
2. Compiling (cc1)
The real brain: parses your C into a syntax tree, checks types, optimizes, and emits assembly — human-readable CPU instructions. Peek with gcc -S hello.c:
main:
push rbp
mov rbp, rsp
lea rdi, [rip+.LC0] ; address of "Hello, World!"
call puts ; the compiler even swapped
mov eax, 0 ; printf for cheaper puts!
pop rbp
ret3. Assembling (as)
Assembly is translated 1-to-1 into binary machine code, producing an object file (hello.o). It's real machine code, but with holes: the address of printf is still unknown — it lives in the C library.
4. Linking (ld)
The linker glues your object files together with libraries, fills in every unresolved address, and emits the final executable. When you see undefined reference to 'foo' — that's the linker telling you a promise (a declaration) was never fulfilled (a definition).
$ gcc -c hello.c # stop after assembling: makes hello.o $ gcc hello.o -o hello # link it into an executable $ ./hello Hello, World! # or do everything at once: $ gcc hello.c -o hello
🧠 Checkpoint: Which stage replaces #include <stdio.h> with the header’s contents?
- Preprocessor
- Compiler
- Assembler
- Linker
Show answer
Preprocessor — The preprocessor is a text-substitution pass that runs before any real C parsing. That’s why it’s called PRE-processor.
🧠 Checkpoint: You get undefined reference to `sqrt'. Whose error is it, and what’s the likely fix?
- Compiler — add a cast
- Preprocessor — include math.h
- Linker — add
-lmto link the math library - Assembler — upgrade gcc
Show answer
Linker — add -lm to link the math library — “Undefined reference” is always the linker. Including math.h satisfies the compiler (declaration), but the definition lives in libm — link it with -lm.
Why should you care? Error messages come from different stages: #include typos → preprocessor · syntax/type errors → compiler · undefined reference → linker. Knowing who is complaining tells you where to look.
🧠 Checkpoint: What does an object file (.o) contain?
- Preprocessed C source
- Assembly text
- Machine code with unresolved addresses
- A complete runnable program
Show answer
Machine code with unresolved addresses — It is genuine machine code, but calls into other files/libraries are placeholders (“relocations”) that the linker resolves later.
Much later, in the final part of this course, we'll dissect each stage in loving detail — flags, optimizations, and how to read the assembly output. For now, the mental model is what matters.
▶ Practice this lesson interactively (with live gcc)