C++ One Definition Rule Across Translation Units
The One Definition Rule (ODR) is a foundational principle in C++ that dictates how entities—such as variables, functions, classes, and templates—can be declared and defined within a program. When a program consists of multiple translation units, the ODR establishes precise boundaries to ensure that the compiler and linker can combine compiled units into a coherent executable without ambiguity or symbol conflicts. This article breaks down how the ODR governs declarations and definitions across translation units, the exceptions that permit identical definitions, and the consequences of rule violations.
Translation Units and Linkage Basics
A translation unit (TU) consists of a single source file together
with all the headers and source code included by preprocessor
#include directives. Because C++ compiles each translation
unit independently into an object file, the linker is responsible for
resolving cross-unit references based on the linkage of declared
entities:
- External Linkage: The name can be referred to from other translation units (e.g., standard non-static functions, global variables).
- Internal Linkage: The name can only be referred to
within its own translation unit (e.g., variables declared
staticor entities within unnamed/anonymous namespaces). - No Linkage: The name can only be referred to within its local scope (e.g., local block variables).
The ODR applies strictly to names with external linkage when coordinating across translation units.
Exactly One Definition for Non-Inline Entities
For non-inline functions and non-inline variables with external linkage, the ODR mandates that there must be exactly one definition in the entire program.
You may declare these entities as many times as necessary across
different translation units (typically via header files using the
extern keyword for variables, or regular function
signatures), but defining them more than once across separate
translation units results in a build failure. For example:
// header.h
extern int globalCounter; // Allowed: declaration across multiple TUs
void logMessage(); // Allowed: declaration across multiple TUs
// file1.cpp
#include "header.h"
int globalCounter = 0; // Valid: single definition
// file2.cpp
#include "header.h"
int globalCounter = 5; // Violation: multiple definition linker errorMultiple Definitions Allowed: Classes, Templates, and Inline Entities
Certain entities are designed to be included across multiple translation units. The ODR explicitly permits the definition of the following constructs in more than one TU:
- Class definitions (structs, classes, unions)
- Enumerations
- Inline functions and inline variables (including functions defined inside class bodies)
- Class and function templates
However, this permission comes with strict constraints. If an entity is defined in more than one TU, all of the following conditions must be satisfied:
- Token-by-Token Match: Each definition must consist of the exact same sequence of tokens.
- Identical Semantic Meaning: Every token must carry the identical meaning in each translation unit. Name lookup within each definition must resolve to the exact same entities across all units.
- Identical Calling Conventions and Layouts: Any sub-objects or internal members must have the same alignment, size, and layout rules applied across all TUs.
If a class definition appears in file1.cpp and
file2.cpp via a shared header, the definition is permitted.
But if the header behavior changes depending on conditional compilation
(#ifdef) macros that differ between the two TUs, the rule
is violated.
Diagnostic Rules and Undefined Behavior
A critical aspect of the ODR across translation units is compiler enforcement:
- Diagnosed Violations: Duplicate definitions of
non-inline functions and global variables are caught by the linker,
yielding an error (such as
multiple definition of...). - Non-Diagnosed Violations: If a class, inline function, or template is defined differently in two translation units, the C++ standard designates this as a violation for which no diagnostic is required.
When a diagnostic is not required, the linker will arbitrarily choose one definition and discard the others without issuing a warning. This leads to undefined behavior, which often manifests as silent memory corruption, mismatched struct offsets, corrupted virtual method tables (vtables), or hard-to-trace runtime crashes.
Preventing ODR Violations
To comply with the One Definition Rule across multiple translation units:
- Define all non-inline functions and global variables in single
implementation files (
.cpp). - Place only declarations, classes, templates, and
inlinedefinitions inside headers. - Use include guards (
#pragma onceor#ifndefguards) to prevent multiple definitions within a single TU. - Avoid using macro definitions to conditionally alter struct or class layouts across different compilation units.