A plan falling apart

In C++ global variables can lead to several issues – especially if defined in header files. Since we just introduced nullopt as a global variable in our project, I expected to encounter such issues, which would have enabled me to talk about them here.

But that didn’t quiet work out – I didn’t ran in such issues. As I found that quiet astonishing, I’ll tell you now what I had in mind at first – and then why the issue I anticipated didn’t occur.

The story I wanted to tell you

Let’s take for a moment this example below: there we have one header file and two implementation (*.cpp) files.

// x.hpp
int x = 5;

In the header file x.hpp there is one variable x defined.

// a.cpp
#include "x.hpp"
int main() {}

The two implementation files a.cpp and b.cpp just include this header file.

// b.cpp
#include "x.hpp"

Let’s now compile and link these files using the commands below.

gcc -o a.o -c a.cpp 
gcc -o b.o -c b.cpp 
gcc a.o b.o

You don’t need to do it manually right now; you can just have a look at it on compiler explorer. However, if we do so we will encounter an linker error (ld being our linker in that case):

/usr/bin/ld: b.o:(.data+0x0): multiple definition of `x'; a.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

The reason for that is pretty much clear: by including x.hpp we introduced the definition of the same variable x in two different translation units. This means that we now have two translation units which define the same globally visible variable. Hence, the two object files a.o and b.o export each the exact same symbol for x. This is not allowed; therefore the linker report’s an error.

The story I wanted to tell would have been:

  1. I’d included optional.hpp in two *.cpp and linked them together.
  2. We’d encountered a linker error stating that nullopt has been defined multiple times.
  3. I’d told you how to fix that issue.

I did that – and it just worked. No Linker whatsoever. I was surprised.

After a bit of fiddling around, going back and forth, I found the issue. It turns out that nullopt is not a global variable. It is a global constant. If we go back to our example above and put a const right in front of x, the linker error disappears.

The particularities of global constants

But why is that? Why behave global variables different than global constants? Well’ it turn out, that they do not only differ in their mutability. They also differ in their linkage. Global variables have external linkage– global constants have internal linkage.

What is now linkage? To put it simple, the linkage of a symbol defines whether the symbol is visible outside of the translation unit or not. A different way to put it is, that symbols with external linkage are visible to the linker, symbols with internal linkage are not.

The C++ Standard defines that “constants on namespace level have internal linkage*. The constants/variables we are looking at right now are defined in the global namespace, so this rule applies here. But it would also hold if we’d put the constants in another namespace.

So the gist of this is: as nullopt is a constant defined in the global namespace it is not visible to the linker and can therefore not cause any linker errors.

Conclusion

C++ can be surprising. That’s nothing new, but holds true especially in this case. Today we learned about the differences between global variables and global constants as well as the difference between external and internal linkage. On the topic of linkage in can recommend this blog post which covers the topic in much more detail.

After this small detour we can (hopefully) proceed with our journey towards a feature complete optional in the next post.