extern
and inline
keywordsIn previous lessons, we introduced the idea of forward declaration. This allows our files to call functions that have been defined in a different file:
// greeting.cpp
#include <iostream>
void SayHello(){
std::cout << "Hello";
}
// main.cpp
void SayHello();
int main(){
SayHello();
}
Hello
Making this work is the job of the linker. After our files are taken by the compiler and converted to a temporary form, sometimes called object files.
After our object files have been created, the linker then combines them to create a final, cohesive package. We covered this process in more detail in our introductory course:
In this lesson, we go a little deeper into this process, introducing some pitfalls we’re likely to encounter, and how to solve them.
Identifiers we use, such as the names of variables or classes, are often referred to as symbols. How a symbol interacts with the linker is commonly called its linkage.
Depending on the symbol, it will have one of three possible linkages by default. However, as we’ll see through this lesson, we can often manipulate our code to modify its linkage.
The three types of linkage are:
Identifiers with external linkage are available anywhere in our program. Examples of external linkage include:
In our initial example, SayHello()
was a free function, so it had external linkage. That’s what made it accessible from a different file.
Identifiers with internal linkage are only accessible within the same file they are declared. The most common things we’ll create that have internal linkage by default are const
and constexpr
variables. However, later in this lesson, we’ll demonstrate how to give other symbols internal linkage.
Symbols defined within block scope (e.g., local variables within a function) have no linkage at all. They are only accessible within the block they are declared.
We’re perhaps already intuitively familiar with the one-definition rule, which prevents us from having multiple definitions for the same symbol, in a way that would cause conflicts. A basic example of such a conflict is this:
int x{1};
int x{2};
But, when we consider external linkage, there are other, less obvious forms of this. The following program has a SayHello()
definition in two places. They’re in two different files but, because free functions have external linkage, this is still a violation of the one-definition rule:
// main.cpp
#include <iostream>
void SayHello(){
std::cout << "Hello";
}
int main(){ SayHello(); }
// greeting.cpp
#include <iostream>
void SayHello(){
std::cout << "Hello";
}
This program will not link correctly, as a function called SayHello()
that accepts no arguments and returns void
is defined in multiple object files:
main.obj : error LNK2005: "SayHello(void)" already defined in greeting.obj
When we want to give a symbol internal linkage, the main way to do this is through an anonymous namespace. Predictably, these are namespaces without names:
// greeting.cpp
#include <iostream>
namespace{
void SayHello() { std::cout << "Hello"; }
}
Symbols within an anonymous namespace are still accessible within the same object file, but no longer accessible to other object files.
In the following example,
SayHello()
, defined within main.cpp
has external linkageSayHello()
, defined within an anonymous namespace within greeting.cpp
has internal linkage and is therefore only accessible to greeting.cpp
Greet()
, defined within greeting.cpp
has external linkage and is therefore accessible to main.cpp
//main.cpp
#include <iostream>
void SayHello(){
std::cout << "Hello from main.obj";
};
void Greet();
int main(){ Greet(); }
// greeting.cpp
#include <iostream>
namespace{
void SayHello(){
std::cout << "Hello from greeting.obj";
}
}
void Greet(){ SayHello(); }
This program is no longer violating the one-definition rule, so compiles and runs successfully:
Hello from greeting.obj
In the previous example, main.cpp
was defining SayHello()
in the global namespace, with external linkage. As such, that definition is still accessible within greeting.cpp
.
However, within greeting.cpp
, calls to SayHello()
will instead use the most local definition of that symbol. That is the definition within the anonymous namespace of that same file.
When two different definitions of a symbol are accessible within the same scope, the more local definition is said to be masking or shadowing the other definition.
In scenarios like this, we can specify which definition of SayHello()
we’re referring to using the scope resolution operator, ::
.
When the scope we’re trying to access is the global scope, we use the unary form of this operator - for example, ::SayHello()
.
Below, main.cpp
is unchanged from the previous example - it is calling the externally linked Greet()
function defined within greeting.cpp
as before.
However, Greet()
is now using the unary ::
operator to call the externally linked SayHello()
function defined within main.cpp
:
// greeting.cpp
#include <iostream>
// Forward declaration
void SayHello();
namespace{
void SayHello(){
std::cout << "Hello from greeting.obj";
}
}
void Greet(){
// Unary :: operator
::SayHello();
}
Hello from main.obj
extern
KeywordSimilar to how we can "forward-declare" a function that will be defined elsewhere, so too we treat variables in the same way.
To declare that a variable will be defined externally, we mark it with the extern
keyword. Below, main.cpp
uses the externally-linked definition of Pi
, defined within math.cpp
:
//main.cpp
#include <iostream>
extern float Pi;
int main(){ std::cout << "Pi: " << Pi; }
// math.cpp
float Pi{3.14f};
Pi: 3.14
We can explicitly prepend extern
to function declarations too, if preferred:
extern void SomeFunction(int Arg);
But, as we’ve already seen in our forward declaration examples, this isn’t necessary. The compiler can implicitly differentiate between a function declaration and a function definition, based on whether or not a function body is provided.
Unlike regular variables, const
and constexpr
variables have internal linkage by default. This can be changed by marking the variable definition as extern
:
// math.cpp
extern const float Pi{3.14f};
In the file where we’re using the global variable, we need to match the const
-ness within our forward declaration. For example, if the definition was a const
, our forward declaration must also be marked as const
:
//main.cpp
#include <iostream>
extern const float Pi;
int main(){ std::cout << "Pi: " << Pi; }
Pi: 3.14
extern
Error: constexpr
object must be initializedThe C++ standard allows constexpr
variables to be declared as extern
, but Visual Studio does not currently allow this by default.
To make Visual Studio conform to the standards here, the /Zc:externConstexpr
option needs to be set in the compiler options. The Visual Studio documentation has instructions on how to do this.
In larger projects, if we’re using global variables at all, we’ll want to define them in one place. This is typically a header file, which we can then #include
wherever it’s needed.
However, this soon leads us to break the one-definition rule, as this simple example demonstrates:
//main.cpp
#include <iostream>
#include "globals.h"
int main(){ std::cout << "Global: " << Global; }
// somefile.cpp
#include "globals.h"
//globals.h
#pragma once
int Global{42};
Now, our translation units (our source files after the preprocessor has implemented all our directives) will have multiple global definitions of the Global
variable:
main.obj : error LNK2005: "int Global" already defined in somefile.obj
We have several ways to circumvent this but, as of C++17, the simplest is to declare the variable as inline
:
//globals.h
#pragma once
inline int Global{42};
With no other changes, our program now works:
Global: 42
In this case, the inline
keyword has the effect of having only one object file maintain the definition of Global
. Every other object file has the replaced with an extern int Global;
declaration.
Since all the definitions originate from the same place - the globals.h
file - it doesn’t matter which definition is kept. All the definitions were equivalent.
In this lesson, we explored linkage, focusing on how extern
, inline
, and anonymous namespaces affect the visibility and accessibility of symbols across different translation units.
extern
keyword is used to declare that a variable is defined externally, allowing for its use across different files.const
and constexpr
variables have internal linkage by default, a behavior that can be overridden with extern
.::
can help access global symbols when local definitions shadow them.A deeper look at the C++ linker and how it interacts with our variables and functions. We also cover how we can change those interactions, using the extern
and inline
keywords
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.