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 static or 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 error

Multiple 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:

  1. Token-by-Token Match: Each definition must consist of the exact same sequence of tokens.
  2. 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.
  3. 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 inline definitions inside headers.
  • Use include guards (#pragma once or #ifndef guards) to prevent multiple definitions within a single TU.
  • Avoid using macro definitions to conditionally alter struct or class layouts across different compilation units.