Inside the C++ Compiler: From Tokens to Assembly

When you compile a C++ program, a surprising amount of work happens behind the scenes. Compilation is not a single transformation from source code to executable, but a carefully designed pipeline of stages that progressively analyze, verify, and transform your code.

Understanding how a compiler works helps you write better C++, interpret error messages more effectively, and reason about performance and optimizations. This article walks through the major stages of a modern C++ compiler, from raw text to machine instructions.

1. Lexical Analysis (Lexing)

The first stage of compilation is lexical analysis, also known as lexing. Here, the compiler reads your source code as plain text and groups characters into meaningful units called tokens.

int sum = a + b;

During lexing, whitespace and comments are discarded, and the statement above is converted into a sequence of tokens:

At this stage, the compiler does not understand meaning or structure. It only recognizes valid symbols of the C++ language.

2. Syntax Analysis (Parsing)

Once tokens are produced, the compiler checks whether they form a valid C++ program. This stage is known as parsing.

The parser organizes tokens according to the C++ grammar and builds an Abstract Syntax Tree (AST), which represents the hierarchical structure of the code.

Parsing answers the question: “Is this code grammatically valid C++?”

Syntax errors such as missing semicolons, mismatched braces, or incorrect expressions are detected here. If parsing fails, compilation stops immediately.

3. Semantic Analysis

After the code is structurally valid, the compiler verifies whether it makes sense. This phase is called semantic analysis.

Errors like using an undeclared variable, calling a function with incorrect argument types, or violating const rules are caught during this phase.

4. Intermediate Representation (IR)

Rather than translating C++ directly into machine code, most modern compilers first lower the program into an Intermediate Representation (IR).

IR is a simplified, platform-independent form that makes analysis and optimization easier. For example, LLVM-based compilers use LLVM IR, while GCC has its own internal representations.

This abstraction allows the compiler to apply powerful optimizations without worrying about specific CPU instructions too early.

5. Optimization

With the program in IR form, the compiler applies a wide range of optimizations. These transformations aim to improve performance, reduce memory usage, or eliminate redundant work—without changing observable behavior.

Optimization levels (-O0, -O2, -O3) control how aggressively these transformations are applied.

C++ → Assembly Example

Consider the following simple C++ function:

int add(int a, int b) {
  return a + b;
}

After compilation, the compiler may generate assembly code similar to:


add:
  mov eax, edi
  add eax, esi
  ret

This assembly directly reflects the calling convention and instruction set of the target CPU. Even simple C++ code can map to very few machine instructions after optimization.

Optimizations can drastically change the generated assembly — debugging optimized builds is significantly harder than debugging unoptimized ones.

Key Takeaways

Understanding how the C++ compiler works gives you deeper insight into performance, error diagnostics, and low-level behavior. For systems programming, this knowledge is not optional—it is essential.