Mixing extern and const
C++17 inline
variables
If you think you want an extern const
, then it is more likely that you would actually want to use C++17 inline variables.
This awesome C++17 feature allow us to:
- conveniently use just a single memory address for each constant
- store it as a
constexpr
: How to declare constexpr extern? - do it in a single line from one header
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif
notmain.cpp
#include "notmain.hpp"
const int* notmain_func() {
return ¬main_i;
}
Compile and run:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main
GitHub upstream.
See also: How do inline variables work?
C++ standard on inline variables
The C++ standard guarantees that the addresses will be the same. C++17 N4659 standard draft 10.1.6 "The inline specifier":
6 An inline function or variable with external linkage shall have the same address in all translation units.
cppreference https://en.cppreference.com/w/cpp/language/inline explains that if static
is not given, then it has external linkage.
Inline variable implementation
We can observe how it is implemented with:
nm main.o notmain.o
which contains:
main.o:
U _GLOBAL_OFFSET_TABLE_
U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i
and man nm
says about u
:
"u" The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.
so we see that there is a dedicated ELF extension for this.
Pre-C++ 17: extern const
extern const
does work as in the example below, but the downsides over inline
are:
- it is not possible to make the variable
constexpr
with this technique, onlyinline
allows that: How to declare constexpr extern? - it is less elegant as you have to declare and define the variable separately in the header and cpp file
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
GitHub upstream.
Pre-C++17 header only alternatives
These are not as good as the extern
solution, but they work and only take up a single memory location:
A constexpr
function, because constexpr
implies inline
and inline
allows (forces) the definition to appear on every translation unit:
constexpr int shared_inline_constexpr() { return 42; }
and I bet that any decent compiler will inline the call.
You can also use a const
or constexpr
static integer variable as in:
#include <iostream>
struct MyClass {
static constexpr int i = 42;
};
int main() {
std::cout << MyClass::i << std::endl;
// undefined reference to `MyClass::i'
//std::cout << &MyClass::i << std::endl;
}
but you can't do things like taking its address, or else it becomes odr-used, see also: https://en.cppreference.com/w/cpp/language/static "Constant static members" and Defining constexpr static data members
Any way to fully inline it?
TODO: is there any way to fully inline the variable, without using any memory at all?
Much like what the preprocessor does.
This would require somehow:
- forbidding or detecting if the address of the variable is taken
- add that information to the ELF object files, and let LTO optimize it up
Related:
- C++11 enum with class members and constexpr link-time optimization
Tested in Ubuntu 18.10, GCC 8.2.0.
- Yes, you can use them together.
- And yes, it should exactly match the declaration in the translation unit it's actually declared in. Unless of course you are participating in the Underhanded C Programming Contest :-)
The usual pattern is:
- file.h:
extern const int a_global_var;
- file.c:
#include "file.h"
const int a_global_var = /* some const expression */;
Edit: Incorporated legends2k's comment. Thanks.
You can use them together and you can do all sorts of things which ignore the const keyword, because that's all it is; a keyword. It tells the compiler that you won't be changing a variable which in turn allows the compiler to do some useful optomisations and stops you from changing things you didn't mean to.
Possibility.com has a decent article with some more background.
You can use them together. But you need to be consistent on your use of const because when C++ does name decoration, const is included in the type information that is used to decorate the symbol names. so extern const int i
will refer to a different variable than extern int i
Unless you use extern "C" {}. C name decoration doesn't pay attention to const.