The C Path — learn C, visually

🧮 Foundations — Before C

From source to silicon: the compiler pipeline

⏱ 13 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

Sooner 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:

hello.s (x86-64 excerpt)
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
        ret

3. 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).

terminal
$ 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 -lm to 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)