Modern C++
Programming
11. Translation Units I
Linkage and One Definition Rule
Federico Busato
2024-03-29
Table of Contents
1 Basic Concepts
Translation Unit
Local and Global Scope
Linkage
2 Storage Class and Duration
Storage Duration
Storage Class
static and extern Keywords
Internal/External Linkage Examples
1/50
Table of Contents
3 Linkage of const and constexpr Variables
Static Initialization Order Fiasco
4 Linkage Summary
5 Dealing with Multiple Translation Units
Class in Multiple Translation Units
2/50
Table of Contents
6 One Deﬁnition Rule (ODR)
Global Variable Issues
ODR - Point (3)
inline Functions/Variables
constexpr and inline
7 ODR - Function Template
Cases
extern Keyword
3/50
Table of Contents
8 ODR - Class Template
Cases
extern Keyword
9 ODR Undeﬁned Behavior and Summary
4/50
Basic Concepts
Translation Unit
Header File and Source File
Header ﬁles allow deﬁning interfaces (.h, .hpp, .hxx), while keeping the
implementation in separated source ﬁles (.c, .cpp, .cxx).
Translation Unit
A translation unit (or compilation unit) is the basic unit of compilation in C++. It
consists of the content of a single source ﬁle, plus the content of any header ﬁle
directly or indirectly included by it
A single translation unit can be compiled into an object ﬁle, library, or executable
program
5/50
Compile Process
6/50
Local and Global Scope
Scope
The scope of a variable/function/object is the region of the code within the entity
can be accessed
Local Scope / Block Scope
Entities that are declared inside a function or a block are called local variables.
Their memory address is not valid outside their scope
Global Scope / File Scope / Namespace Scope
Entities that are deﬁned outside all functions.
They hold a single memory location throughout the life-time of the program
7/50
Local and Global Scope
int var1; // global scope
int f() {
int var2; // local scope
}
struct A {
int var3; // depends on where the instance of 'A' is used
};
8/50
Linkage
Linkage
Linkage refers to the visibility of symbols to the linker
No Linkage
No linkage refers to symbols in the local scope of declaration and not visible to the
linker
Internal Linkage
Internal linkage refers to symbols visible only in scope of a single translation unit.
The same symbol name has a diﬀerent memory address in distinct translation units
External Linkage
External linkage refers to entities that exist ( visible/accessible) outside a single
translation unit. They are accessible and have the same identical memory address
through the whole program, which is the combination of all translation units
9/50
Storage Class and
Duration
Storage Duration 1/2
Storage Duration
The storage duration (or duration class) determines the duration of a variable,
namely when it is created and destroyed
Storage Duration Allocation Deallocation
Automatic Code block start Code block end
Static Program start Program end
Dynamic Memory allocation Memory deallocation
Thread Thread start Thread end
en.cppreference.com/w/cpp/language/storage duration
10/50
Storage Duration 2/2
• Automatic storage duration. Local variables temporary allocated on registers or
stack (depending on compiler, architecture, etc.).
If not explicitly initialized, their value is undeﬁned
• Static storage duration. The storage of an object is allocated when the program
begins and deallocated when the program ends.
If not explicitly initialized, it is zero-initialized
• Dynamic storage duration. The object is allocated and deallocated by using
dynamic memory allocation functions ( new/delete ).
If not explicitly initialized, its memory content is undeﬁned
• Thread storage duration C++11. The object is allocated when the thread
begins and deallocated when the thread ends. Each thread has its own instance of
the object
11/50
Storage Duration Examples
int v1; // static duration
void f() {
int v2; // automatic duration
auto v3 = 3; // automatic duration
auto array = new int[10]; // dynamic duration (allocation)
} // array, v2, v3 variables deallocation (from stack)
// the memory associated to "array" is not deallocated
int main() {
f();
}
// main end: v1 is deallocated
12/50
Storage Class
Storage Class Speciﬁer
The storage class for a variable declaration is a type speciﬁer that, together with
the scope, governs its storage duration and linkage
Storage Class Notes Scope Storage Duration Linkage
auto lo cal var decl. Lo cal automatic No linkage
no storage class global var decl. Global static External
static Local static
Function
Dependent
static Global static Internal
extern Global static External
thread local C++11 any thread local any
13/50
Storage Class Examples
int v1; // no storage class
static int v2 = 2; // static storage class
extern int v3; // external storage class
thread_local int v4; // thread local storage class
thread_local static int v5; // thread local and static storage classes
int main() {
int v6; // auto storage class
auto v7 = 3; // auto storage class
static int v8; // static storage class
thread_local int v9; // thread local and auto storage classes
auto array = new int[10]; // auto storage class ("array" variable)
}
14/50
Local static Variables
static local variables are allocated when the program begins, initialized when the
function is called the ﬁrst time, and deallocated when the program ends
int f() {
static int val = 1;
val++;
return val;
}
int main() {
cout << f(); // print 2 ("val" is initialized)
cout << f(); // print 3
cout << f(); // print 4
}
15/50
static and extern Keywords
static /anonymous namespace-included global variables or functions are visible only
within the ﬁle → internal linkage
• Non- static global variables or functions with the same name in diﬀerent translation
units produce name collision (or name conﬂict)
extern keyword is used to declare the existence of global variables or functions in
another translation unit → external linkage
• the variable or function must be deﬁned in one and only one translation unit
• it is redundant for functions
• it is necessary for variables to prevent the compiler to associate a memory location in the
current translation unit
If the same identiﬁer within a translation unit appears with both internal and external linkage,
the behavior is undeﬁned
16/50
Internal/External Linkage Examples
int var1 = 3; // external linkage
// (in conflict with variables in other
// translation units with the same name)
static int var2 = 4; // internal linkage (visible only in the
// current translation unit)
extern int var3; // external linkage
// (implemented in another translation unit)
void f1() {} // external linkage (could conflict)
static void f2() {} // internal linkage
namespace { // anonymous namespace
void f3() {} // internal linkage
}
extern void f4(); // external linkage
// (implemented in another translation unit)
17/50
Linkage of const
and constexpr
Variables
Linkage of const and constexpr Variables
const variables have internal linkage at global scope
constexpr variables imply const , which implies internal linkage
note: the same variable has diﬀerent memory addresses on diﬀerent translation units (code
bloat)
const int var1 = 3; // internal linkage
constexpr int var2 = 2; // internal linkage
static const int var3 = 3; // internal linkage (redundant)
static constexpr int var4 = 2; // internal linkage (redundant)
int main() {}
18/50
Static Initialization Order Fiasco 1/2
In C++, the order in which global variables are initialized at runtime is not deﬁned.
This introduces a subtle problem called static initialization order ﬁasco
source.cpp
int f() { return 3; } // run-time function
int x = f(); // run-time evalutation
main.cpp
extern int x;
int y = x; // run-time initialized
int main() {
cout << y; // print "3" or "0" depending on the linking order
}
19/50
Static Initialization Order Fiasco 2/2
source.cpp
constexpr int f() { return 3; } // compile-time/run-time function
constinit int x = f(); // compile-time initialized (C++20)
main.cpp
constinit extern int x; // compile-time initialized (C++20)
int y = x; // run-time initialized
int main() {
cout << y; // print "3"!!
}
20/50
Linkage Summary
Linkage Summary 1/2
No Linkage: Local variables, functions, classes
• static local variable address depends on the linkage of its function
Internal Linkage:
(not accessible by other translation units, no conﬂicts, diﬀerent memory addresses)
• Global Variables:
◦ static
◦ non-inline, non-template, non-specialized, non-extern const / constexpr
• Functions: static
• Anonymous namespace content, even structures/classes
21/50
Linkage Summary 2/2
External Linkage:
(accessible by other translation units, potential conﬂicts, same memory address)
• Global Variables:
◦ no specifier, or extern
◦ template/specialized C++14 (no conﬂicts for template , see ODR)
◦ inline const / constexpr C++17 (no conﬂicts, see ODR)
• Functions:
◦ no specifier (no conﬂicts with inline , see ODR), or extern
◦ template/specialized (no conﬂicts for template , see ODR)
Note: inline , constexpr (which implies inline for functions) functions are not
accessible by other translation units even with external linkage
• Enumerators, Classes and their static, non-static members
22/50
Dealing with
Multiple Translation
Units
Code Structure 1
• one header, two source ﬁles → two translation units
• the header is included in both translation units
23/50
Code Structure 2
• two headers, two source ﬁles → two translation units
• one header for declarations (.hpp), and the other one for implementations
(.i.hpp)
• the header and the header implementation are included in both translation units
* separate header declaration and implementation is not m andatory, but it could help to better
organize the code
24/50
Class in Multiple Translation Units 1/2
header.hpp:
class A {
public:
void f();
static void g();
private:
int x;
static int y;
};
main.cpp:
# include "header.hpp"
# include <iostream>
int main() {
A a;
std::cout << A.x; // print 1
std::cout << A::y; // print 2
}
source.cpp:
# include "header.hpp"
void A::f() {}
void A::g() {}
int A::x = 1;
int A::y = 2;
25/50
Class in Multiple Translation Units 2/2
header.hpp:
struct A {
static int y; // zero-init
// static int y = 3; // compile error
// must be initialized out-of-class
const int z = 3; // only in C++11
// const int z; // compile error
// must be initialized
static const int w1; // zero-init
static const int w2 = 4; // inline-init
};
source.cpp:
# include "header.hpp"
int A::y = 2;
const int A::w1 = 3;
26/50
One Deﬁnition Rule
(ODR)
One Deﬁnition Rule (ODR)
(1) In any (single) translation unit, a template, type, function, or object, cannot
have more than one deﬁnition
- Compiler error otherwise
- Any number of declarations are allowed
(2) In the entire program, an object or non-inline function cannot have more
than one deﬁnition
- Multiple deﬁnitions linking error otherwise
- Entities with internal linkage in diﬀerent translation units are allowed, even if their
names and types are the same
(3) A template, type, or inline functions/variables, can be deﬁned in more than
one translation unit. For a given entity, each deﬁnition must be the same
- Undeﬁned behavior otherwise
- Common case: same header included in multiple translation units
27/50
ODR - Point (1), (2)
header.hpp:
void f(); // DECLARATION
main.cpp:
# include "header.hpp"
# include <iostream>
int a = 1; // external linkage
// int a = 7; // compiler error, Point (1)
extern int b;
static int c = 2; // internal linkage
int main() {
std::cout << a; // print 1
std::cout << b; // print 5
std::cout << c; // print 2
f();
}
source.cpp:
# include "header.hpp"
# include <iostream>
// linking error, multiple definitions
// int a = 2; // Point (2)
int b = 5; // ok
// internal linkage
static int c = 4; // ok
void f() { // DEFINITION
// std::cout << a; // 'a' is not visible
std::cout << b; // print 5
std::cout << c; // print 4
}
28/50
Global Variable Issues - ODR Point (2)
header.hpp:
# include <iostream>
struct A {
A() { std::cout << "A()"; }
∼A() { std::cout << "∼A()"; }
};
// A obj; // linking error multiple definitions, Point (2)
const A const_obj{}; // "const/constexpr" implies internal linkage
constexpr float PI = 3.14f;
source1.cpp:
# include "header.hpp"
void f() { std::cout << &PI; }
// address: 0x1234ABCD
// print "A()" the first time
// print "∼A()" the first time
source2.cpp:
# include "header.hpp"
void f() { std::cout << &PI; }
// print address: 0x3820FDAC !!
// print "A()" the second time!!
// print "∼A()" the second time!!
29/50
Common Class Error - ODR Point (2)
header.hpp:
struct A {
void f() {}; // inline DEFINITION
void g(); // DECLARATION
void h(); // DECLARATION
};
void A::g() {} // DEFINITION
main.cpp:
# include "header.hpp"
// linking error
// multiple definitions of A::g()
int main() {}
source.cpp:
# include "header.hpp"
// linking error
// multiple definitions of A::g()
void A::h() {} // DEFINITION, ok
30/50
ODR - Point (3)
ODR Point (3): A template, type, or inline functions/variables, can be
deﬁned in more than one translation unit
• The linker removes all deﬁnitions of an inline / template entity except one
• All deﬁnitions must be identical to avoid undeﬁned behavior due to arbitrary
linking order
• inline / template entities have a unique memory address across all translation
units
• inline / template entities have the same linkage as the corresponding
variables/functions without the speciﬁer
31/50
inline Functions/Variables 1/2
inline
inline speciﬁer allows a function or a variable (in C++17) to be identically
deﬁned (not only declared) in multiple translation units
• inline is one of the most misunderstood features of C++
• inline is a hint for the linker. Without it, the linker can emit “multiple
definitions” error
• inline entities cannot be exported, namely, used by other translation units even
if they have external linkage (related warning: -Wundefined-inline )
• inline doesn’t mean that the compiler is forced to perform function inlining. It
just increases the optimization heuristic threshold
32/50
inline Functions/Variables 2/2
void f() {}
inline void g() {}
f() :
• Cannot be deﬁned in a header included in multiple source ﬁles
• The linker issues a “multiple deﬁnitions” error
g() :
• Can be deﬁned in a header and included in multiple source ﬁles
33/50
constexpr and inline
constexpr functions are implicitly inline
constexpr variables are not implicitly inline . C++17 added inline variables
void f1() {} // external linkage
// potential multiple definitions error
constexpr void f2() {} // external linkage, implicitly inline
// multiple definitions allowed
constexpr int x = 3; //
internal linkage
// different files allows distinct definitions
// -> different addresses, code bloat
inline constexpr int y = 3; // external linkage unique memory address
// -> potential undefined behavior
int main() {}
34/50
One Deﬁnition Rule - Point (3) 1/2
header.hpp:
inline void f() {} // the function is marked 'inline' (no linking error)
inline int v = 3; // the variable is marked 'inline' (no linking error) (C++17)
template<typename T>
void g(T x) {} // the function is a template (no linking error)
using var_t = int; // types can be defined multiple times (no linking error)
main.cpp:
# include "header.hpp"
int main() {
f();
g(3); // g<int> generated
}
source.cpp:
# include "header.hpp"
void h() {
f();
g(5); // g<int> generated
}
35/50
One Deﬁnition Rule - Point (3) 2/2
Alternative organization:
header.hpp:
inline void f(); // DECLARATION
inline int v; // DECLARATION
template<typename T>
void g(T x); // DECLARATION
using var_t = int; // type
# include "header.i.hpp"
header.i.hpp:
void f() {} // DEFINITION
int v = 3; // DEFINITION
template<typename T>
void g(T x) {} // DEFINITION
main.cpp:
# include "header.hpp"
int main() {
f();
g(3); // g<int> generated
}
source.cpp:
# include "header.hpp"
void h() {
f();
g(5); // g<int> generated
}
36/50
ODR - Function
Template
Function Template - Case 1
header.hpp:
template<typename T>
void f(T x) {}; // DECLARATION and DEFINITION
main.cpp:
# include "header.hpp"
int main() {
f(3); // call f<int>()
f(3.3f); // call f<float>()
f('a'); // call f<char>()
}
source.cpp:
# include "header.hpp"
void h() {
f(3); // call f<int>()
f(3.3f); // call f<float>()
f('a'); // call f<char>()
}
f<int>() , f<float>() , f<char>() are generated two times (in both translation units)
37/50
Function Template - Case 2
header.hpp:
template<typename T>
void f(T x); // DECLARATION
main.cpp:
# include "header.hpp"
int main() {
f(3); // call f<int>()
f(3.3f); // call f<float>()
// f('a'); // linking error
} // the specialization does not exist
source.cpp:
# include "header.hpp"
template<typename T>
void f(T x) {} // DEFINITION
// template SPECIALIZATION
template void f<int>(int);
template void f<float>(float);
// any explicit instance is also
// fine, e.g. f<int>(3)
38/50
Function Template and Specialization
header.hpp:
template<typename T>
void f() {} // DECLARATION and DEFINITION
main.cpp:
# include "header.hpp"
int main() {
f<char>(); // use the generic function
f<int>(); // use the specialization
}
source.cpp:
# include "header.hpp"
template<>
void f<int>() {} // SPECIALIZATION
// DEFINITION
39/50
Function Template - extern Keyword
C++11
header.hpp:
template<typename T>
void f() {} // DECLARATION and DEFINITION
main.cpp:
# include "header.hpp"
extern template void f<int>();
// f<int>() is not generated by the
// compiler in this translation unit
int main() {
f<int>();
}
source.cpp:
# include "header.hpp"
void g() {
f<int>();
}
// or 'template void f<int>(int);'
40/50
ODR Function Template Common Error
header.hpp:
template<typename T>
void f(); // DECLARATION
// template<> // linking error
// void f<int>() {} // multiple definitions -> included twice
// full specializations are like standard functions
// it can be solved by adding "inline"
main.cpp:
# include "header.hpp"
int main() {}
source.cpp:
# include "header.hpp"
// some code
41/50
ODR - Class
Template
Class Template - Case 1
header.hpp:
template<typename T>
struct A {
T x = 3; // "inline" DEFINITION
void f() {}; // "inline" DEFINITION
};
main.cpp:
# include "header.hpp"
int main() {
A<int> a1; // ok
A<float> a2; // ok
A<char> a3; // ok
}
source.cpp:
# include "header.hpp"
int g() {
A<int> a1; // ok
A<float> a2; // ok
A<char> a3; // ok
}
42/50
Class Template - Case 2
header.hpp:
template<typename T>
struct A {
T x;
void f(); // DECLARATION
};
# include "header.i.hpp"
header.i.hpp:
template<typename T>
T A<T>::x = 3; // DEFINITION
template<typename T>
void A<T>::f() {} // DEFINITION
main.cpp:
# include "header.hpp"
int main() {
A<int> a1; // ok
A<float> a2; // ok
A<char> a3; // ok
}
source.cpp:
# include "header.hpp"
int g() {
A<int> a1; // ok
A<float> a2; // ok
A<char> a3; // ok
}
43/50
Class Template - Case 3
header.hpp:
template<typename T>
struct A {
T x;
void f(); // DECLARATION
};
main.cpp:
# include "header.hpp"
int main() {
A<int> a1; // ok
// A<char> a2; // linking error
} // 'f()' is undefined
// while 'x' has an undefined
// value for A<char>
source.cpp:
# include "header.hpp"
template<typename T>
int A<T>::x = 3; // initialization
template<typename T>
void A<T>::f() {} // DEFINITION
// generate template specialization
template class A<int>;
44/50
Class Template - extern Keyword
C++11
header.hpp:
template<typename T>
struct A {
T x;
void f() {}
};
source.cpp:
# include "header.hpp"
extern template class A<int>;
// A<int> is not generated by the
// compiler in this translation unit
int main() {
A<int> a;
}
source.cpp:
# include "header.hpp"
// template specialization
template class A<int>;
// or any instantiation of A<int>
45/50
ODR Undeﬁned
Behavior and
Summary
Undeﬁned Behavior - inline Function
main.cpp:
# include <iostream>
inline int f() { return 3; }
void g();
int main() {
std::cout << f(); // print 3
std::cout << g(); // print 3!!
} // not 5
source.cpp:
// same signature and inline
inline int f() { return 5; }
int g() { return f(); }
The linker can arbitrary choose one of the two deﬁnitions of f() . With -O3 , the
compiler could inline f() in g() , so now g() return 5
This issue is easy to detect in trivial examples but hard to ﬁnd in large codebase
Solution: static or anonymous namespace
46/50
Undeﬁned Behavior - Member Function
header.hpp:
# include <iostream>
struct A {
int f() { return 3; }
};
int g();
main.cpp:
# include "header.hpp"
int main() {
A a;
std::cout << a.f();// print 3
std::cout << g(); // print 3!!
}
source.cpp:
struct A {
int f() { return 5; }
};
int g() {
A<int> a;
return a.f();
}
47/50
Undeﬁned Behavior - Function Template
header.hpp:
template<typename T>
int f() {
return 3;
}
int g();
main.cpp:
# include "header.hpp"
int main() {
std::cout << f<int>(); // print 3
std::cout << g(); // print 3!!
}
source.cpp:
template<typename T>
int f() {
return 5;
}
int g() {
return f<int>();
}
48/50
Undeﬁned Behavior
Other ODR violations are even harder (if not impossible) to ﬁnd, see Diagnosing
Hidden ODR Violations in Visual C++
Some tools for partially detecting ODR violations:
• -detect-odr-violations ﬂag for gold/llvm linker
• -Wodr -flto ﬂag for GCC
• Clang address sanitizer + ASAN OPTIONS=detect odr violation=2
(link)
Another solution could be included all ﬁles in a single translation unit
49/50
ODR - Declarations and Deﬁnitions Summary
• Header: declaration of
- functions, structures, classes, types, alias
- template functions, structs, classes
- extern variables, functions
• Header (implementation): deﬁnition of
- inline variables/functions
- template variables/functions/classes
- global static, non-static
const/constexpr variables and constexpr
functions
• Source ﬁle: deﬁnition of
- functions, including template full specializations
- classes
- extern and static global variables/functions
50/50