Skip to main content

C++ Build Pipeline: Compilation vs Linking vs Loading Explained

How the three stages of the C++ build pipeline — compilation, linking, and loading — compare, what each one transforms, and which one your build error usually comes from. The pillar guide that ties the three deep-dives together.

The C++ build pipeline runs every program through three distinct stages between your source code and a running process: compilation turns each .cpp file into an object file with placeholder symbols, linking combines those object files and libraries into one executable with real addresses, and loading is what the operating system does at runtime to map that executable into memory and resolve any remaining dynamic symbols. This article compares the three stages side-by-side, shows what each transforms, and maps common build errors to the stage that produced them. The deeper dives on each stage live in dedicated articles.

The three stages, at a glance

StageWhenInputOutputOwnsTypical errors
CompilationBuild time (per file)One .cpp translation unitOne .o object filePreprocessing, parsing, template instantiation, codegen, optimizationerror: 'foo' was not declared, template errors, syntax errors
LinkingBuild time (per executable)All .o files + static/dynamic libsOne executable or shared librarySymbol resolution, relocation, section mergingundefined reference to 'foo', multiple-definition errors, missing libraries
LoadingRun time (per launch)An executable + its dependenciesA process running at main()Mapping segments into the address space, dynamic-linker symbol resolution, constructor executionerror while loading shared libraries, version 'GLIBC_X.Y' not found, symbol lookup error

Two rules of thumb:

  • Compilation errors talk about your code. They reference line numbers and identifiers from your source.
  • Linking and loading errors talk about symbols. They reference mangled or unmangled names with no file/line — because by then the compiler is gone and only the symbol table remains.

Compilation: source → object file

Each .cpp translation unit is processed independently. The compiler preprocesses (#include, #define), parses to an AST, instantiates templates, applies optimization passes, and emits machine code into an object file (.o on Linux/macOS, .obj on Windows). The object file contains the compiled code plus a symbol table that lists every symbol this translation unit defines and every external symbol it references.

Crucially, the compiler doesn't know where any external symbol lives in memory. Calls to std::vector::push_back or to functions defined in other translation units leave relocations in the object file — placeholders that say "the linker will fill this in." This is why compilation can succeed independently for every source file even if your program won't link.

Deep dive: The C++ Compilation Process.

Linking: object files + libraries → executable

The linker reads every object file plus any static libraries (.a) and shared libraries (.so / .dll) listed on its command line. For each external symbol referenced in an object file, the linker scans the others until it finds a definition, then patches the relocation with the real address (for static linking) or a stub that will be resolved at load time (for dynamic linking).

Linking is also where sections are merged: .text from every object file concatenates into the final .text, the symbol tables merge, debug info is rewritten, and constructors are wired into the program's initialization list.

Static vs dynamic linking is the major split:

  • Static linking copies library code directly into the executable. The binary is self-contained but larger, and library bug fixes require a rebuild.
  • Dynamic linking leaves library calls as stubs and defers resolution to load time. The binary is smaller and shares code with other processes using the same .so, but launches more slowly and is sensitive to library version drift.

Deep dive: C++ Linking in Depth.

Loading: executable → running process

When you run ./my-program, the operating system's loader (or the dynamic linker ld.so for dynamically linked binaries) does several things before main() runs:

  1. Parses the executable's headers (ELF on Linux, Mach-O on macOS, PE on Windows) to find each segment.
  2. Maps segments into the process address space using mmap.text read-only and executable, .rodata read-only, .data and .bss read-write.
  3. Loads required shared libraries by recursively reading their headers and mapping their segments. The linker order matters: each .so lists its own dependencies.
  4. Resolves dynamic symbols by patching the GOT (Global Offset Table) and PLT (Procedure Linkage Table). Lazy binding defers per-function resolution until the first call.
  5. Runs constructors in dependency order: global C++ objects' constructors, then the special _init function, then __attribute__((constructor)) functions.
  6. Jumps to main.

Most loader errors surface here — missing shared library, glibc version mismatch, ABI incompatibility, constructor SIGSEGV.

Deep dive: C++ Loading at Runtime.

Which stage caused this error?

A useful shortcut for triaging build errors:

If the error says...The stage is...
error: 'foo' was not declared in this scopeCompilation
error: invalid use of incomplete typeCompilation
Pages of template error messagesCompilation
undefined reference to 'foo'Linking
multiple definition of 'foo'Linking
cannot find -lboost_systemLinking
error while loading shared libraries: libfoo.so: cannot openLoading
version 'GLIBC_2.34' not foundLoading
symbol lookup error: ./app: undefined symbolLoading

If the error references a line number, it's compilation. If it references a symbol but no file, it's linking. If it shows up only when you run the program, it's loading.

Reading further

The three deep dives go far past this overview:

Abhik Sarkar

Abhik Sarkar

Machine Learning Consultant specializing in Computer Vision and Deep Learning. Leading ML teams and building innovative solutions.

Share this article

If you found this article helpful, consider sharing it with your network

Mastodon