The extern Keyword

After all the recent posts about statics, I wanted to talk about the extern keyword before moving onto something different.

Do you remember the problem we had with our “global” static variable in this post? If you haven’t read it, the code example illustrated that static variables are only available to code in the translation unit. That means that if you include a file with a static variable in several of your other files, you won’t just have one copy of that variable. This is really important, because you could run into a nasty bug if you are accessing the static variable thinking that it is the same one across all the files you are using it in.

The extern keyword provides the solution to this, as it allows you to create a true global variable that will be available across all files and all translation units.

The extern keyword comes from the C language and is used before the declaration of a variable:

extern int temp;

Note that if you include a definition AND the extern keyword, the extern is ignored:

extern int temp = 0;

The line above is seen by the compiler as:

int temp = 0;

And you should get a warning if you attempt to do this (in gcc).

So, the extern keyword is only to be used to tell the code in your current file that the variable in question exists somewhere. It doesn’t say anything else about that variable.

With that in mind, let’s alter the program in the above-mentioned post to use extern instead of static. As before, it has four files, although this time instead of including a header with a static variable, let’s change the file to a .cpp file and use extern to tell the rest of the code about it.

Compile the code with:

g++ main.cpp DiskDrive.cpp Storage.cpp

File 1: main.cpp

#include <iostream>
#include "DiskDrive.h"

//We are telling the compiler this variable exists somewhere else.
extern int storage;

int main()
{
    std::cout << "Storage: " << storage << " TB" << std::endl;

    DiskDrive d1;
    DiskDrive d2;

    d1.Initialise();
    storage += 1024;

    d2.Initialise();
    storage += 1024;

    std::cout << "Storage: " << storage << " TB" << std::endl;
    std::cout << "Storage: " << d2.GetSystemStorageTotal();
    std::cout << " TB" << std::endl;
}

File 2: Storage.cpp

int storage = 0;

File 3: DiskDrive.cpp

#include <iostream>
#include "DiskDrive.h"

//We are telling the compiler this variable exists somewhere else.
extern int storage;

//constructor
DiskDrive::DiskDrive() {}

//destructor
DiskDrive::~DiskDrive(){}

void DiskDrive::Initialise()
{
    //do setup
}

int DiskDrive::GetSystemStorageTotal()
{
    return storage; //the static "global" variable
}

File 4: DiskDrive.h

class DiskDrive
{
public:
    DiskDrive();
    ~DiskDrive();
    void Initialise();
    int GetSystemStorageTotal();
};

The output of this program is as follows:

Storage: 0 TB
Storage: 2048 TB
Storage: 2048 TB

What’s going on here then?

Well, first of all you can see that we have the output we expect – storage is consistently shown at the adjusted amount (2048) from calls via main.cpp and DiskDrive.cpp. This wasn’t the case with the previous example that used the static keyword.

Secondly, you should note that we do not explicitly include the storage variable anywhere via a header file. We use the extern keyword to tell the compiler that the linker will know all about the variable. Note: you can, of course, include it in a header file, but I have deliberately not done so here to show you that the compiler is happy with the extern even though it doesn’t know where to look to find the variable.

Thirdly, note that the extern’d variable should be defined only once. This is done in the Storage.cpp file where it is initially set to zero.

In practice, it is common to place extern variables in a separate header file and include that header as necessary for each source file.

Can you use extern for anything else?

Yes. You can also use extern for functions and classes. I’ll leave it up to you to try out – but let me know if you’d like examples.

Anything else?

Yes. One more thing: the extern keyword can be used to link code from different languages – like C, Fortran, and assembler.

You can include C code, and assembler and Fortran that conforms to a C implementation, in your program with:

extern "C" <function declaration>

Then just include the function definition as normal.

There is an excellent article on mixing C and C++ on the isocpp website, so I won’t repeat what’s already been explained beautifully there.