What a Compiler Actually Does to Your Code
Source text becomes a running program through four distinct stages — lexing, parsing, optimization, and code generation — using C++'s ahead-of-time pipeline as the walkthrough.
Compiling code from source text into something a processor can execute isn't one step — it's a pipeline of distinct stages, each transforming the program into a different representation. Using a C++ compiler like GCC or Clang as the concrete example, the pipeline runs lexing, parsing, optimization, and code generation, in that order.
Lexing (or tokenizing) is the first stage, and the simplest to describe: it reads the raw source text character by character and groups it into tokens — keywords, identifiers, operators, literals, punctuation — discarding whitespace and comments along the way. The line int x = 5; becomes a stream of tokens like int, x, =, 5, and ; with no structure between them yet, just a flat sequence.
Parsing takes that flat token stream and imposes structure on it, building an abstract syntax tree (AST) that reflects how the tokens relate to each other grammatically — which expression is nested inside which statement, which arguments belong to which function call. This is also where syntax errors get caught: a token stream that can't be assembled into a valid tree according to the language's grammar is a syntax error, reported at this stage.
Optimization operates on an intermediate representation derived from that tree, applying transformations that preserve the program's observable behavior while making it faster or smaller — eliminating dead code that can never execute, hoisting a loop-invariant computation out of the loop instead of recalculating it every iteration, inlining a small function's body directly at its call site to avoid the function-call overhead entirely.
Code generation is the final stage, translating the optimized intermediate representation into actual machine instructions for a specific target processor architecture — x86-64 and ARM64 require genuinely different instructions for the same logical operation, which is why a compiled binary only runs on the architecture it was generated for. This is also where a C++ compiler differs sharply from a JIT-compiled or interpreted language: all four stages complete ahead of time, producing a finished binary before the program ever runs, rather than compiling incrementally while the program executes.
Every stage in this pipeline is independently documented in depth by the major compiler projects — GCC's and LLVM's own documentation both describe their internal pass structure well beyond what a single article can cover — for anyone who wants to go past "what happens" into exactly how each transformation is implemented.
