Program Structure and Compilation

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Translation Units

Each .cpp file, after the preprocessor expands every #include into it, is one translation unit (TU) — the unit the compiler actually compiles into one .o/.obj object file. A program links one or more TUs together:

Headers included into .cpp source files become translation units

Declarations vs. Definitions

A declaration introduces a name and its type; a definition additionally reserves storage or supplies a body. A name can be declared many times but defined only once per program (see the ODR below):

// declarations -- can appear in as many TUs as include this header
extern int counter;          // declares a variable defined elsewhere
int square(int x);           // declares a function defined elsewhere

// definitions -- each may exist in exactly one TU
int counter = 0;             // defines (and declares) the variable
int square(int x) { return x * x; }   // defines (and declares) the function

The One Definition Rule (ODR)

The One Definition Rule says every non-inline function/variable/class used in a program must have exactly one definition across the whole program (inline functions/variables and templates are the sanctioned exception — they may be defined identically in every TU that uses them, which is exactly why they are safe to put in headers). Violating the ODR is undefined behavior, often not even diagnosed by the linker, so it is worth internalizing which of these two go in a header:

// point.h
#pragma once

struct Point {                 // class definitions in headers are fine -- an implicit ODR exception
    int x, y;
};

inline int distanceSquared(Point a, Point b) {   // "inline" makes a header-defined function ODR-safe
    int dx = a.x - b.x, dy = a.y - b.y;
    return dx * dx + dy * dy;
}

int area(Point a, Point b);    // a NON-inline function must only be *declared* here, and defined
                                // exactly once in a single .cpp -- defining it here too would violate the ODR
                                // as soon as two .cpp files include this header.

Headers and Include Guards

#pragma once (supported by every mainstream compiler, though not standardized) or the portable #ifndef/#define/#endif idiom prevents a header’s contents from being pasted twice into the same TU:

#ifndef POINT_H
#define POINT_H

struct Point { int x, y; };

#endif // POINT_H

Linkage

A name’s linkage controls whether the same name in a different TU refers to the same entity:

  • External linkage (the default for non-const/non-static free functions and globals) — visible to other TUs at link time.

  • Internal linkage (static at namespace scope, or an unnamed namespace, or const/constexpr globals by default) — private to this TU; another TU’s identically-named static entity is a different entity, not a clash.

  • No linkage — local variables, function parameters.

static int fileLocalCounter = 0;   // internal linkage -- invisible outside this TU

namespace {
    void helper() { /* ... */ }    // unnamed namespace -- the modern, preferred way to say "internal linkage"
}

main

Every program has exactly one main, with one of two standard signatures:

int main();                              // no command-line arguments
int main(int argc, char* argv[]);        // argc = argument count, argv[0] = program name

Returning from main (or falling off its end) implicitly returns 0; return EXIT_SUCCESS;/return EXIT_FAILURE; (from <cstdlib>) are the portable spellings of "succeeded"/"failed" for the hosting environment.

A First Look at Modules

C++20 modules replace #include-based textual inclusion with a compiled interface, avoiding both preprocessor leakage and repeated header parsing:

// math.cppm -- a module interface unit
export module math;

export int square(int x) {
    return x * x;
}
// main.cpp
import math;

int main() {
    return square(6) - 30;   // 6*6 - 30 == 6
}

Modules need build-system support that varies by toolchain and is still maturing; see Namespaces, Modules, and the Preprocessor for module partitions and feature-test details, and Build and Tooling for the current state of modules support in CMake/GCC/Clang/MSVC.

Static vs. Dynamic Libraries

  • A static library (.a on Unix, .lib on Windows) is an archive of .o files, copied into the final executable at link time — larger binaries, no runtime dependency.

  • A dynamic/shared library (.so on Linux, .dylib on macOS, .dll on Windows) is linked by reference and loaded at process start (or `dlopen`ed later) — smaller binaries, shared code pages across processes, but the library must be present (and ABI-compatible) at run time.

# static
g++ -c mathlib.cpp -o mathlib.o
ar rcs libmath.a mathlib.o
g++ main.cpp -L. -lmath -o app

# dynamic
g++ -shared -fPIC mathlib.cpp -o libmath.so
g++ main.cpp -L. -lmath -o app -Wl,-rpath,'$ORIGIN'

See Also