Minimal runnable example
This awesome C++17 feature allow us to:
- conveniently use just a single memory address for each constant
- optionally mark it as a
constexpr: How to declare constexpr extern? (there was no convenient way of doing this previously I think) - 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.
GCC 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
Before C++ 17, and in C, we can achieve a very similar effect with an extern const, which will lead to a single memory location being used.
The downsides over inline are:
- it is not possible to make the variable
constexpr with this technique, only inline 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
C
In C the situation is the same as C++ pre C++ 17, I've uploaded an example at: What does "static" mean in C?
The only difference is that in C++, const implies static for globals, but it does not in C: C++ semantics of `static const` vs `const`
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:
Tested in Ubuntu 18.10, GCC 8.2.0.
const.inlinekeyword does for functions. Theinlinekeyword, when applied to functions, has one other crucial effect, which translates directly to variables. Aninlinefunction, that's presumably declared in a header file, will not result in "duplicate symbol" errors at link time, even if the header gets#included by multiple translation units. Theinlinekeyword, when applied to variables, will have the same exact result. The End.inlineis only a weak, non-binding request to the optimiser. Compilers are free to not inline requested functions and/or to inline ones you didn't annotate. Rather, the actual purpose of theinlinekeyword is to circumvent multiple definition errors.