How Extern Works Across Translation Units in C++
In C++, the extern keyword extends the visibility of
variables and functions beyond the boundaries of a single translation
unit. By declaring a symbol with extern, you inform the
compiler that the symbol exists and has storage allocated elsewhere,
deferring the resolution of its actual memory address to the linking
stage. This mechanism is essential for sharing global state, accessing
cross-module functions, and preventing multiple-definition errors under
the One Definition Rule (ODR).
Translation Units and Compilation
A translation unit (TU) consists of a single source file
(.cpp) along with all the headers directly or indirectly
included in it. The C++ compiler processes each TU independently,
converting source code into an object file (.o or
.obj). Because the compiler operates on one TU in
isolation, it cannot locate identifiers defined in a completely separate
source file without explicit guidance.
Declarations vs. Definitions
Understanding extern requires a distinction between a
definition and a declaration:
- A definition specifies the type, reserves storage (allocates memory), and potentially initializes the symbol. Under C++'s One Definition Rule, a non-inline variable or function can only have one definition across the entire program.
- A declaration merely announces the existence, name, and type of a symbol to the compiler without allocating storage.
When applied to a variable, extern acts strictly as a
declaration, provided it is not accompanied by an initializer:
// Translation Unit A: defining the variable
// file_a.cpp
int globalCounter = 42; // Definition with external linkage
// Translation Unit B: referencing the variable
// file_b.cpp
extern int globalCounter; // Declaration: informs compiler that storage is elsewhere
void increment() {
globalCounter++;
}If you initialize an extern variable (e.g.,
extern int x = 10;), it is treated as a definition,
defeating its use as an external reference within that scope.
The Role of the Linker
When compiling file_b.cpp, the compiler encounters
globalCounter and verifies its type against the
extern declaration. Because there is no memory address yet
assigned to it within that unit, the compiler generates a placeholder
entry in the object file’s symbol table, categorizing the symbol as an
unresolved external reference.
Once all TUs are compiled into object files, the linker combines them
into an executable or shared library. The linker inspects the exported
symbol table of file_a.o (which defines
globalCounter) and updates the placeholder in
file_b.o with the actual memory address assigned to
globalCounter. If the definition cannot be found in any
translation unit, the linker raises an "undefined reference" error. If
defined in more than one unit, it raises a "duplicate symbol" error.
Functions and Internal Linkage Nuances
Functions in C++ have extern linkage by default.
Declaring void printMessage(); in a header file is
implicitly extern void printMessage();. The
extern keyword is therefore rarely written out for
functions unless dealing with language linkage.
However, non-local variables behave differently, particularly
const and constexpr variables. In C++, a
const global variable defaults to internal linkage (static
visibility), meaning it cannot be referenced outside its own translation
unit. To expose a const variable to another TU, you must
explicitly mark both the definition and the declaration with
extern:
// constants.cpp
extern const int MaxLimit = 100; // Definition with external linkage
// main.cpp
extern const int MaxLimit; // DeclarationLanguage Linkage:
extern "C"
In addition to linkage visibility, extern is used to
specify language linkage, primarily with extern "C". C++
employs name mangling to encode parameter types into function symbol
names to support function overloading. The C language does not use name
mangling.
Using extern "C" instructs the C++ compiler to generate
unmangled symbol names for the specified functions, enabling C++
translation units to reference functions compiled in pure C object
files, or allowing C code to consume C++ definitions.