Internal and External Linkage

A deeper look at the C++ linker, how it interacts with our variables and functions, and how we can control it using the extern and inline keywords
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

a83.jpg
Ryan McCombe
Ryan McCombe
Posted

In previous lessons, we introduced the idea of forward declaration. This allows our files to call functions that have been defined in a different file:

// main.cpp
void SayHello();

int main(){ SayHello(); }
// greeting.cpp
#include <iostream>

void SayHello(){ std::cout << "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 connects them together to create a final, cohesive package.

Our previous SayHello() program compiles, and links successfully to create the expected output:

Hello

In this lesson, we go a little deeper on how that works, and some pitfalls we’re likely to encounter.

Linkage

How a symbol interacts with the linker is commonly called its linkage. There are three types of linkage and the things we create, like variables, functions, and classes will have one of these types of linkage by default. However, as we’ll see through this lesson, we can often manipulate our code to change that behavior.

The three types of linkage are:

External Linkage

Identifiers with external linkage are available anywhere in our program. Examples of external linkage include:

  • Most global variables
  • Free functions - i.e., functions that are not part of a class, such as the SayHello function in our initial example
  • Classes, structs, and enums

Class names, most global variables, and “free functions” (i.e., functions that are not part of a class) have external linkage. In the previous example, SayHello() was a free function, so it had external linkage. That’s what made it accessible from a different file.

Internal Linkage

Identifiers with internal linkage are only accessible within the same file they are declared. The most common thing we’ll create that has internal linkage by default are const and constexpr variables. However, later in this lesson, we’ll demonstrate how to give other symbols internal linkage.

No 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.

One Definition Rule (ODR)

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{5};
int x{5};

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

Internal Linkage with Anonymous Namespaces

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. That means our previous program is no longer violating the one definition rule:

//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(); }
Hello from greeting.obj

In this example, main.cpp is defining SayHello() in the global namespace. As such, that definition is still accessible within greeting.cpp.

If we wanted to use it there, we would need to forward declare it in the usual way. Then, when we call the function, we need to specify that we want to call the symbol that is defined within the global namespace.

We do that through the scope resolution operator ::, but without a left operand:

// greeting.cpp
#include <iostream>

// Forward declaration
void SayHello(); 

namespace{
  void SayHello(){
    std::cout << "Hello from greeting.obj";
  }
}

void Greet(){
  // Unary :: operator
  ::SayHello();
}

The unary :: operator indicates we want to access a symbol in the global namespace which, in this case, is the definition of SayHello() originating from main.cpp:

Hello from main.obj

Externally Defined Variables with the extern Keyword

Similar to how we can “forward-declare” a function that will be defined elsewhere, so too we treat variables in the same way.

To declare a variable will be defined externally, we mark it with the extern keyword:

//main.cpp
#include <iostream>

extern float Pi;

int main(){ std::cout << "Pi: " << Pi; }
// somefile.cpp
float Pi{3.14f};
Pi: 3.14

Linkage of const and constexpr Variables

Unlike regular variables, const and constexpr variables have internal linkage by default. This can be changed by marking the variable definition as extern:

// somefile.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 declaration. For example, if the definition was a const, our declaration must also be marked as const:

//main.cpp
#include <iostream>

extern const float Pi;

int main(){ std::cout << "Pi: " << Pi; }
Pi: 3.14

Visual Studio extern Error: constexpr object must be initialized

The 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.

Inline Variables

In larger projects, if we’re using global variables at all, we’ll want to define global variables 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.

Was this lesson useful?

Edit History

  • — First Published

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Static Class Variables and Functions

A guide to sharing values between objects using static class variables and functions
373.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved