Clang/LLVM 7 and 8 on Windows initialize an inline static data member once per TU. As far as I understand C++17 this is not correct.
Although an inline variable may be defined in multiple TUs the compiler and/or linker must ensure that it exists only once in a program and hence is initialized exactly once.
The following little program shows what happens with Clang/LLVM (tested in Visual Studio 2017 and 2019 RC with LLVM Compiler Toolchain extension):
// header.h
#include <iostream>
struct A
{
A() { std::cout << "ctor " << this << std::endl; }
~A() { std::cout << "dtor " << this << std::endl; }
void f() { std::cout << "f " << this << std::endl; }
};
struct S
{
inline static A a; // C++17 inline variable, thus also a definition
};
// TU1.cpp
#include "header.h"
int main()
{
S::a.f();
}
// TU2.cpp
#include "header.h"
// TU3.cpp
#include "header.h"
// TU4.cpp
#include "header.h"
This program prints:
ctor 010D4020
ctor 010D4020
ctor 010D4020
ctor 010D4020
f 010D4020
dtor 010D4020
dtor 010D4020
dtor 010D4020
dtor 010D4020
That's four initializations for the one and only object of A (in fact one per TU) instead of exactly one (as C++17 demands).
The program should print:
ctor 010D4020
f 010D4020
dtor 010D4020
This is what MSVC does, by the way.
This is a bug in clang/LLVM, right?
The primary feature of the inline
keyword is that it amends the ODR rule in two ways:
Multiple definitions (with some restrictions) are allowed
The resulting objects are "folded" into a single instance:
An inline function or variable with external linkage shall have the same address in all translation units.
The only addition in C++17 is that it also allows a static
data member declaration to be a definition. That's it.
A static
data member still has the same linkage (external in your case), storage duration and lifetime, and for all practical purposes works just like a globally defined variable.
See [class.static.data]/6:
Static data members are initialized and destroyed exactly like non-local variables
That means essentially that it should work the same as this:
struct A
{
A() { std::cout << "ctor "; }
~A() { std::cout << "dtor "; }
};
A a; // in one of the TU's
extern A a; // in all other TU's
Conclusion:
It's a bug in Clang. The static
S::a
must be initialized and destroyed once.
This bug is fixed in the current snapshot build based on SVN r361807.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With