In the prior lesson (7.6 -- Internal
linkage), we discussed how internal linkage limits the use of an identifier to a single file.
In this lesson, we’ll explore the concept of external linkage.
An identifier with external linkage can be seen and used both from the file in which it is defined, and from other code files (via a forward declaration). In this sense, identifiers with external linkage are truly “global” in that they can be used anywhere in your program!
Key insight
Identifiers with external linkage are visible to the linker. This allows the linker to do two things:
- Connect an identifier used in one translation unit with the appropriate definition in another translation unit.
- Deduplicate inline identifiers so one canonical definition remains. We discuss inline variables and functions in lesson 7.9 -- Inline functions and variables.
Functions have external linkage by default
In lesson 2.8 -- Programs with multiple code files, you learned that you can call a function defined in one file from another file. This is because functions have external linkage by default.
In order to call a function defined in another file, you must place a forward declaration for the
function in any other files wishing to use the function. The forward declaration tells the compiler about the
existence of the function, and the linker connects the function calls to the actual function definition.
Here’s an example:
a.cpp:
main.cpp:
The above program prints:
Hi!
In the above example, the forward declaration of function sayHi() in main.cpp allows
main.cpp to access the sayHi() function defined in a.cpp. The forward
declaration satisfies the compiler, and the linker is able to link the function call to the function definition.
If function sayHi() had internal linkage instead, the linker would not be able to connect the
function call to the function definition, and a linker error would result.
Global variables with external linkage
Global variables with external linkage are sometimes called external variables. To make a
global variable external (and thus accessible by other files), we can use the extern keyword to do
so:
Non-const global variables are external by default, so we don’t need to mark them as extern.
Variable forward declarations via the extern keyword
To actually use an external global variable that has been defined in another file, you also must place a
forward declaration for the global variable in any other files wishing to use the variable. For
variables, creating a forward declaration is also done via the extern keyword (with no
initialization value).
Here is an example of using variable forward declarations:
main.cpp:
Here’s the definitions for those variables:
a.cpp:
In the above example, a.cpp and main.cpp both reference the same global variable
named g_x. So even though g_x is defined and initialized in a.cpp, we are
able to use its value in main.cpp via the forward declaration of g_x.
Note that the extern keyword has different meanings in different contexts. In some contexts,
extern means “give this variable external linkage”. In other contexts, extern means
“this is a forward declaration for an external variable that is defined somewhere else”. Yes, this is confusing,
so we summarize all of these usages in lesson 7.12 -- Scope, duration, and
linkage summary.
Warning
If you want to define an uninitialized non-const global variable, do not use the extern keyword, otherwise C++ will think you’re trying to make a forward declaration for the variable.
Warning
Although constexpr variables can be given external linkage via the extern keyword, they can not
be forward declared as constexpr. This is because the compiler needs to know the value of the constexpr
variable (at compile time). If that value is defined in some other file, the compiler has no visibility on
what value was defined in that other file.
However, you can forward declare a constexpr variable as const, which the compiler will treat as a runtime const. This isn’t particularly useful.
Note that function forward declarations don’t need the extern keyword -- the compiler is able to
tell whether you’re defining a new function or making a forward declaration based on whether you supply a
function body or not. Variables forward declarations do need the extern keyword to help
differentiate uninitialized variables definitions from variable forward declarations (they look otherwise
identical):
Avoid using extern on a non-const global
variable with an initializer
The following two lines are semantically equivalent:
However, your compiler may issue a warning about the latter statement, even though it is technically valid.
Remember when we said compilers have the leeway to issue a diagnostic for things they find suspicious? This is
one of those cases. Conventionally, extern is applied to a non-const variable when we want a
forward declaration. However, adding an initializer makes the statement a definition instead. The compiler is
telling you that something seems amiss. To correct it, either remove the initializer (if you intended a forward
declaration) or remove the extern (if you intended a definition).
Best practice
Only use extern for global variable forward declarations or const global variable
definitions.
Do not use extern for non-const global variable definitions (they are implicitly
extern).
Quick summary
We provide a comprehensive summary in lesson 7.12 -- Scope, duration, and linkage summary.