Static and Extern in C++ — What it really does to your program?

Ryonald Teofilo
6 min readJul 30, 2023

--

Static and Extern in C++

C++ is a popular programming language, but it is also known to be difficult to grasp. One of the obscure concepts in the language is the use of static and extern.

// what makes these different?
int y;
static int x;
extern int z;

Seeing the static keyword for the first time, I was convinced that it is enough to just understand it as way to tell the compiler to keep an object alive as long as the program is running. However, I very soon realise that this assumption is nowhere near enough, as I started stumbling upon it inside functions, classes and even static functions!

// inside a class?
class Foo
{
static int x;
};

// inside a function?
void Foo()
{
static int x;
}

// static function?
static void Bar()
{
// do Bar() things
}

Things got even hazier as I came across extern.

// what does this mean? can we use 'x' here?
extern int x;

Just like any other dev, I scoured online for answers. But many other C++ programmers would also tell you, most C++ references on the internet can be rather vague or really dense that it might overwhelm a newcomer :(

Hence, I have written this short piece to explain the purpose behind static and extern — in a way that I wish I was told.

Internal and External Linkage

Before we get into what these keywords do. We first need to go over the concept of internal and external linkage.

In C++, a single source file (.cpp or .cxx file) represents a single translation unit — “translated” by the compiler.

A compiled source file produces an object file (.o file) that will be linked with other object files to form an executable (.exe) or library (.a).

A symbol (a variable or function) in C++ could either have internal or external linkage.

Internal Linkage: Not visible from outside of the current translation unit.

External Linkage: Visible from other translation units.

Non-const variables and free functions have external linkage by default, whilst constexpr, const, typedef and static objects default to internal linkage.

Extern

The extern keyword gives an object external linkage. Let’s take a look at the code below to see what this means.

// ====== file1.cpp ======
// global variable
int gGlobal = 0;


// ====== file2.cpp ======
// tells the linker to look for my definition elsewhere!
extern int gGlobal;

// set the same gGlobal declared in file1.cpp to 42
gGlobal = 42;

Say we have an integer variable gGlobal which we declare and define in file1.cpp.

We then create a new file file2.cpp to write some other unrelated code, but we later realise we need access gGlobal for whatever reason.

In order to access that variable, we need to locally declare it in the current translation unit, so we write extern int gGlobal — which essentially tells our linker that the definition for gGlobal exists somewhere outside of the current translation unit.

Important note, extern only declares a variable, it does not define it.

int i; // declaration and definition
extern int i; // declaration only

For complete-ness, if we define variables with extern, the keyword will be ignored by the compiler.

extern int i = 43; // extern is ignored on definitions

The semantics of this keyword applies to functions as well. However, it is redundant in this case as function declaration and definition has a clear syntax difference i.e. void Foo(); vs void Foo(){}.

So, when we write a function declaration like this:

void Foo();

the compiler treats it as:

extern void Foo();

C++ and C Linkage

Another common use extern is to specify the type of linkage. You might have seen extern "C" used before a function is declared or defined. This tells our compiler that we want C linkage (C programming language) for the following symbol.

extern "C" int Foo(int foo, int bar)
{
// do Foo() things
}

Since C++ introduced the ability to overload functions, it distinguishes functions with same name by mangling them based on the parameters they take. This effectively means that code compiled in C would not be able to link to symbols from code compiled in C++.

extern “C” comes useful in this case as it allows C++ code to link to libraries compiled in C, and vice versa.

For instance, foo.h here can be included by either a C or C++ source file, with its implementation written and compiled in C++.

// ====== foo.h ======
#ifdef __cplusplus
// function overloading is C++ only
int Foo(int a);
double Foo(double a);

extern "C" {
#endif // __cplusplus

int FooInt(int a);
double FooDouble(double a);

#ifdef __cplusplus
}
#endif // __cplusplus
// ====== foo.cpp ======
#include "Foo.h"
int Foo(int a)
{
return ++a;
}

double Foo(double a)
{
return a + 0.01f;
}

extern "C" int FooInt(int a)
{
return Foo(a);
}

extern "C" double FooDouble(double a)
{
return Foo(a);
}

Static

static specifies that a symbol has static storage duration — meaning that its lifetime lasts throughout the program’s execution. However, the behaviour of the static symbol varies depending on where it is declared.

Namespace scope

When static is used in a namespace scope i.e. outside of a function and class scope, it gives the symbol internal linkage. This means that each translation unit will have its own copy of the symbol, and that it would not be visible to other units.

This also applies to free functions, which means that the function declared as static will not be visible to other translation units. This helps reduce linking time.

// each translation unit will have its own copy of x
static int x;

// each translation unit will have its own copy of Foo()
static void Foo()
{
// do Foo() things
}

An important note here is to never define these in header files, except for constexpr. This is because doing so causes each translation unit to have its own copy of the symbol, which can get confusing really quickly!

Class scope

When a class member is declared as static, it means that it is shared by all the instances of that class. This is useful to store some sort of a shared state, as it could be changed and accessed by all instances of that class.

If this static member is a function, it can be called without an instance of that class, which makes it different to your normal member function.

// ====== apple.h ======
class Apple
{
public:
Apple();

// shared between class instances
static int GetNumOfApple();
private:
static int sNumOfApple;
};
// ====== apple.cpp ======
#include "apple.h"
int Apple::sNumOfApple= 0;

Apple::Apple()
{
sNumOfApple++;
}

int Apple::GetNumOfApple()
{
return sNumOfApple;
}
// -- main.cpp--
#include "apple.h"
int main()
{
Apple a1;
Apple a2;
std::cout << Apple::GetNumOfApple();
}

Building and running this code produces the expected output.

$ g++ -o app main.cpp apple.cpp
$ ./app
2

Function scope

If a static variable in declared inside a function scope, the same instance will be shared between calls of the function. And it will not be accessible outside of the function.

The initialisation of the variable will only be done once, which is during the very first call to the function.

#include <iostream>

int GetNumberOfTimesCalled()
{
static int callFrequency = 0; // initialised once at the first call
callFrequency++;
return callFrequency;
}

int main()
{
for(size_t i = 0; i < 5; i++)
{
std::cout << GetNumberOfTimesCalled() << " ";
}
}
$ g++ -o app main.cpp
$ ./app
1 2 3 4 5

It is noteworthy that this initialisation is thread-safe! This is the reason it is very often we see this used as a getter for a singleton.

Singleton* Singleton::GetSingleton() const
{
static Singleton instance; // Initialised during first call
return &instance;
}

This technique is also used to avoid the infamous Static Initialisation Order Fiascoa scenario where the initialisation of a static variable depends on another static being initialised first.

Hopefully this article has helped cleared any uncertainty around the usage of static and extern keyword in C++! These keywords can be downright confusing initially. Fortunately, it is one of those that sticks with you once you understand it.

Feel free to leave a comment if you have any questions, I will try my best to answer them :)

--

--

Ryonald Teofilo
Ryonald Teofilo

Written by Ryonald Teofilo

Sharing my thoughts on programming, one debug break at a time <ryonaldteofilo.github.io>

Responses (1)