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 #include and 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 (-O0 vs -O2) dramatically changes the assembly the compile stage emits.