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:
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-staticfree functions and globals) — visible to other TUs at link time. -
Internal linkage (
staticat namespace scope, or an unnamed namespace, orconst/constexprglobals by default) — private to this TU; another TU’s identically-namedstaticentity 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 (
.aon Unix,.libon Windows) is an archive of.ofiles, copied into the final executable at link time — larger binaries, no runtime dependency. -
A dynamic/shared library (
.soon Linux,.dylibon macOS,.dllon 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
-
Getting Started — the four compilation stages.
-
Build and Tooling — CMake targets that wrap these commands.
-
C: Program Structure — the same translation-unit and one-definition model, without name mangling,
extern "C", or modules.