Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clang/LLVM 7 and 8 on Windows initialize inline static data member multiple times (with both link.exe and lld-link.exe)

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?

like image 246
x y Avatar asked Jun 22 '18 07:06

x y


2 Answers

The primary feature of the inline keyword is that it amends the ODR rule in two ways:

  1. Multiple definitions (with some restrictions) are allowed

  2. 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.

like image 125
rustyx Avatar answered Sep 24 '22 18:09

rustyx


This bug is fixed in the current snapshot build based on SVN r361807.

like image 20
x y Avatar answered Sep 24 '22 18:09

x y