The Compilation Pipeline
Follow your source code through preprocessing, compilation, assembly, and linking, and see exactly where assembly fits in.
From Text File to Running Program
When you press "build", it feels like one step: source code goes in, a program comes out. In reality the compiler runs your code through a short assembly line of distinct stages. Knowing these stages tells you exactly where assembly appears and why you can ask the compiler to stop and show it to you.
For a single source file, the pipeline has four stages:
preprocess compile assemble link
main.cpp -------> main.ii ------> main.s ------> main.o ------> program
(source) (translation (assembly) (object file) (executable)
unit)
Let us walk through each one.
Stage 1: Preprocessing
The preprocessor handles every line that begins with #. It pastes in the contents of your #include headers, expands macros, and strips out comments. It does not understand C++ at all; it just manipulates text.
The result is a single, large block of pure C++ called a translation unit. A tiny #include <iostream> can expand into tens of thousands of lines once all the headers it pulls in are pasted together.
You can see this stage on its own:
g++ -E main.cpp
The -E flag means "stop after preprocessing". The output is still C++, just with all the includes and macros resolved.
Stage 2: Compilation
This is the heart of the process. The compiler takes the translation unit (pure C++) and translates it into assembly for your target architecture. This is the stage where all the heavy lifting happens: parsing, type checking, and crucially, optimization.
This is also the stage that produces the assembly you are learning to read. You can ask the compiler to stop right here:
g++ -S main.cpp
The -S flag means "stop after compilation, before assembling". It produces a .s file containing human-readable assembly text. Every time this course shows you assembly, this is the stage that generated it.
The optimization level matters enormously here. Compare these two:
g++ -O0 -S main.cpp # no optimization: verbose, literal, easy to follow
g++ -O2 -S main.cpp # heavy optimization: compact, clever, what ships
We will study both throughout the course. -O0 output is closest to your source and is the best place to start learning. -O2 output is what you actually run in production.
Stage 3: Assembling
The assembler takes the human-readable assembly (.s) and turns it into machine code, packaged into an object file (.o). This is almost a direct translation, since assembly and machine code map one-to-one. The object file contains real machine instructions, but it is not yet runnable: it may refer to functions that live in other files and whose addresses are not yet known.
g++ -c main.cpp # preprocess + compile + assemble, stop at the .o file
Stage 4: Linking
The linker combines all the object files, plus the parts of the standard library you used, into a single executable. Its main job is to resolve references: when main.o calls a function defined in another object file, the linker fills in the real address. The output is a program your operating system can load and run.
g++ main.cpp -o program # run the whole pipeline, produce an executable
Where Assembly Lives in All This
The single most useful idea from this lesson is simple:
Assembly is the output of the compile stage (
-S). It is the last human-readable form of your program before it becomes raw machine code.
That is why reading assembly is so valuable. It is the closest readable thing to what actually runs, captured at the exact moment the compiler finished making all its decisions, including every optimization.
One File or Many
Everything above describes a single source file, but real programs have many. The first three stages, preprocess, compile, and assemble, happen independently for each .cpp file, producing one object file each. Only the final link stage brings them all together. This is why each .cpp is often called a translation unit, and why a change to one source file only requires recompiling that file before relinking.
A Quick Mental Model
If you remember nothing else, remember the flag for each stage:
| Flag | Stops after | Output | Readable? |
|---|---|---|---|
-E |
Preprocessing | Expanded C++ | Yes (C++) |
-S |
Compilation | Assembly (.s) |
Yes (assembly) |
-c |
Assembling | Object file (.o) |
No (machine code) |
| (none) | Linking | Executable | No (machine code) |
The -S row is the one this whole course is built around.
Key Takeaways
- Compilation is a pipeline: preprocess -> compile -> assemble -> link.
- The preprocessor (
-E) resolves#includeand macros, producing a translation unit of pure C++. - The compiler (
-S) translates that C++ into assembly and performs optimization. This is the stage that produces the assembly you read. - The assembler (
-c) turns assembly into machine code inside an object file. - The linker combines object files and libraries into a runnable executable.
- Optimization level (
-O0vs-O2) dramatically changes the assembly the compile stage emits.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
The Compilation Pipeline - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!