C++ Initialisierungsreihenfolge von static inline membern

OP #6005375
Lesenswert?

Grüß euch

Ich steh leider bei einem Segfault grad am Schlauch und zweifel an 
meinen eigenen Fähigkeiten. Kann mir jemand verifizieren dass folgende 
zwei Snippets stets in der richtigen Reihenfolge initialisiert werden:
1
struct S {
2
  static inline int i{42};
3
  static inline int* pi{&i};
4
};
5

6
template<typename T>
7
struct S {
8
  static inline T t{42};
9
  static inline T* pt{&t};
10
};

Oder lässt der Standard es zu, dass die Pointer zuerst initialisiert 
werden?

/edit
Typo
OP #6005400
Lesenswert?

Ja den Part hab ich auch grad eben gelesen.

Gut, dann hat Clang9 so wie ich das seh momentan einen ziemlich seriösen 
Bug. Ich hab ein Szenario gefunden in dem der Compiler die 
Initialisierung umdreht unter der Vorraussetzung, dass es sich um eine 
Template Klasse mit static inline member handelt.

Zieht man die Initialisierung der static member aus der Klasse raus, 
dann stimmt die Reihenfolge wieder...


/edit
Ahh... Vergiss die Mehrfachbenamsung und stell dir vor die beiden S 
haben unterschiedlichen Namen. :D
Gast #6005458
Lesenswert?

So wir ich das sehe, ist die Reihenfolge hier in der Praxis egal. Ob i, 
bzw. t initialisiert ist, ist beim referenzieren dieser irrelevant, auf 
deren adresse hat das nähmlich keinen einfluss. Und auf die 
globalen/static dinger zugreifen sollte man ja sowieso erst, wenn diese 
längst alle initialisiert wurden, sonst kommt man so oder so in teufels 
küche.
OP #6005515
Lesenswert?

Ja, wie bereits erwähnt gibts den Spaß nur in Kombi template + static 
inline. Ganz so simpel ist es aber dann leider auch nicht weil der 
Fehler nur auftritt wenn die Klassen recht verschachtelt sind...

Nachvollziehen können hab ich es durch folgenden Code hier. Die Klasse A 
besitzt 2x static inline Member aus der Boost Asio Bibliothek. Einmal 
einen io_context und einen steady_timer, der den io_context als 
Dependency besitzt.
1
#include <boost/asio.hpp>
2
#include <iostream>
3

4
template<typename T>
5
struct A {
6
  static void run() {
7
    std::thread{[] {
8
      t_.expires_after(std::chrono::seconds{1});
9
      t_.async_wait(handler);
10
      io_.run();
11
    }}
12
        .detach();
13
  }
14

15
  static void stop() {
16
    while (!io_.stopped())
17
      ;
18
  }
19

20
  static void handler(boost::system::error_code error) {
21
    std::cout << "fired\n";
22
  }
23

24
  static inline boost::asio::io_context io_;
25
  static inline boost::asio::steady_timer t_{io_};
26
};
27

28
//template<typename T>
29
//boost::asio::io_context A<T>::io_;
30
//template<typename T>
31
//boost::asio::steady_timer A<T>::t_{A::io_};
32

33
using MyA = A<void>;
34

35
int main() {
36
//  volatile auto io_adr{&MyA::io_};
37
//  volatile auto t_adr{&MyA::t_};
38
  MyA::run();
39
  MyA::stop();
40
}

Compiliert via:
1
clang++ -std=c++17 main.cpp -lpthread


fällt folgender Assembler Code hinten raus.
1
25                  static inline boost::asio::steady_timer t_{io_};
2
000055555555b854:   cmpb    $0x0,0x26b65(%rip)        # 0x5555555823c0 
3
000055555555b85b:   jne     0x55555555b8a3 <__cxx_global_var_init.13(void)+83>
4
...
5
000055555555b8a4:   retq    
6
   
7
24                  static inline boost::asio::io_context io_;
8
                  __cxx_global_var_init.14(void):
9
000055555555b8b0:   push    %rbp
10
...
11
000055555555b8f9:   retq

Man sieht recht deutlich dass der steady_timer vor dem io_context 
initialisiert wird. Dummerweise erstellt der io_context intern einen 
neuen Service der vom steady_timer genutzt werden muss. Existiert der 
nicht, dann segfaulted das Programm beim Versuch ein posix_lock zu 
setzen.

Die oben auskommentierten Teile stellen Workarounds dar. Clang dreht die 
Reihenfolge der Initialisierung um wenn man die Variablen als normale 
static member anlegt, bzw. wenn man innerhalb von main() explizit zuerst 
eine vor der anderen Variable angreift...
Gast #6005623
Lesenswert?

Du könntest auch eine Klasse machen, die beides als normale member hat, 
und davon dann eine Instanz static anlegen.

mh schrieb:
> 11.4.8.2 Static data members
> 6 Static data members are initialized and destroyed exactly like
> non-local variables.
>
> So wie ich das sehe, ist die Reihenfolge festgelegt

Ist sie für non local wirklich festgelegt?
Gast #6005642
Lesenswert?

Sebastian schrieb:
> Ist sie für non local wirklich festgelegt?

The storage for objects with static storage duration (basic.stc.static) 
shall be zero-initialized (dcl.init) before any other initialization 
takes place. Zero-initialization and initialization with a constant 
expression are collectively called static initialization; all other 
initialization is dynamic initialization. Objects of POD types 
(basic.types) with static storage duration initialized with constant 
expressions (expr.const) shall be initialized before any dynamic 
initialization takes place. Objects with static storage duration defined 
in namespace scope in the same translation unit and dynamically 
initialized shall be initialized in the order in which their definition 
appears in the translation unit.

https://stackoverflow.com/questions/1421671/when-are-static-c-class-members-initialized
OP #6005688
Lesenswert?

Der Bug wurde gerade mit folgenden Worten zugedreht:
"There is no defined initialization order for implicitly instantiated 
variables."


http://eel.is/c++draft/basic.start.dynamic#1.sentence-1

Der Standard sagt dazu dann folgendes:
"Dynamic initialization of a non-local variable with static storage 
duration is unordered if the variable is an implicitly or explicitly 
instantiated specialization."

Das heißt im Klartext dass auch das Eingangs von mir gepostet Beispiel 
mit dem Template und dem Pointer in keiner definierten Reihenfolge 
erfolgt. Ich glaub das muss ich jetzt erstmal verdauen...
Gast #6005706
Lesenswert?

Vincent H. schrieb:
> Der Bug wurde gerade mit folgenden Worten zugedreht:
> "There is no defined initialization order for implicitly instantiated
> variables."
>
> http://eel.is/c++draft/basic.start.dynamic#1.sentence-1
>
> Der Standard sagt dazu dann folgendes:
> "Dynamic initialization of a non-local variable with static storage
> duration is unordered if the variable is an implicitly or explicitly
> instantiated specialization."
>
> Das heißt im Klartext dass auch das Eingangs von mir gepostet Beispiel
> mit dem Template und dem Pointer in keiner definierten Reihenfolge
> erfolgt. Ich glaub das muss ich jetzt erstmal verdauen...

Da ist jetzt noch zu klären, ob es dynamic initialization ist.
Gast #6005750
Lesenswert?

Vincent H. schrieb:
> Das heißt im Klartext dass auch das Eingangs von mir gepostet Beispiel
> mit dem Template und dem Pointer in keiner definierten Reihenfolge
> erfolgt. Ich glaub das muss ich jetzt erstmal verdauen...

Was? Nee...

Together, zero-initialization and constant initialization are called 
static initialization; all other initialization is dynamic 
initialization.
All static initialization strongly happens before any dynamic 
initialization.
OP #6005779
Lesenswert?

noone schrieb:
> Vincent H. schrieb:
>> Das heißt im Klartext dass auch das Eingangs von mir gepostet Beispiel
>> mit dem Template und dem Pointer in keiner definierten Reihenfolge
>> erfolgt. Ich glaub das muss ich jetzt erstmal verdauen...
>
> Was? Nee...
>
> Together, zero-initialization and constant initialization are called
> static initialization; all other initialization is dynamic
> initialization.
> All static initialization strongly happens before any dynamic
> initialization.

Ja stimmt. Mit dem einfachen Pointer Beispiel kommt man noch nicht in 
die Situation.


Ja ich benötige dem Context für jede Spezialisierung, aber darum gehts 
ja jetzt eh nicht. Ich brauch auch keine Lösungsvorschläge. Ich wollt 
einfach nur wissen ob dieses (imho absurde Verhalten) normal ist...
OP #6006025
Lesenswert?

Und der nächste Bug-Report, diesmal dürfte ich allerdings richtig 
liegen:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92134

Langsam versteh ich nun auch die Motivation hinter dem neuen constinit 
Keyword.


Mein Tip für eine Zukunft mit constinit specifier ist:
Static non-local sollte immer einen constinit specifier besitzen. 
Schreit der Compiler dabei, dann sollte man sich gaaaaaanz genau 
anschaun was passiert.

Ich vermute dass so ein ähnlicher Vorschlag auch bald in Styleguides zu 
finden sein wird.
#6007683
Lesenswert?

Ich bin ja gespannt, ob das mit der Einführung von Modulen gefixt wird. 
Begründung für die ungeordnete Initialisierung schien gewesen zu sein, 
dass der Compiler halt nicht weiß, in welcher Translation Unit welche 
Initialisierung steht. Mit Modulen sollte das ja potentiell kein Problem 
mehr darstellen.
OP #6007721
Lesenswert?

Das von dir beschriebene "static initialization order fiasco" ist 
eigentlich bereits seit C++17 gelöst. Seit C++17 gibt es "static inline 
variables", die es erlauben statische Variablen in Headern anzulegn. Die 
Reihenfolge der Initialisierung ist dann vom Standard klar definiert.

Der Thread allerdings handelt von einem noch subtileren Problem. Und 
zwar von der nicht definierten Reihenfolge bei der dynamischen 
Initialisierung von Spezialisierungen. Leider ist das static Keyword 
derart Überladen dass es einer Raketenwissenschaft ähnelt hier 
durchzublicken...


Ich vermute dass Problem ist darauf zurückzuführen, dass static member 
einer Spezialisierung bis zum ersten Referenzieren des Compilers quasi 
nicht existieren.

Das heißt in folgendem Program
1
template<typename>
2
struct S {
3
    static inline int a{42};
4
    static inline int b{43};
5
};
6

7
S<void> s;
8

9
int main() {
10
    return s.a;
11
}

gibts s.b einfach schlichtweg nicht...

Wenn man das Beispiel dann noch auf dynamische Initialisierung ausweitet 
erkennt man langsam wieso der Standard hier keine Reihenfolge 
vorschreibt. :(
#6007729
Lesenswert?

Vincent H. schrieb:

> Das heißt in folgendem Programtemplate<typename>
> struct S {
>     static inline int a{42};
>     static inline int b{43};
> };
>
> S<void> s;
>
> int main() {
>     return s.a;
> }
>
> gibts s.b einfach schlichtweg nicht...
>
> Wenn man das Beispiel dann noch auf dynamische Initialisierung ausweitet
> erkennt man langsam wieso der Standard hier keine Reihenfolge
> vorschreibt. :(

Moment  - mit dynamischer Initialisierung müsste er das s.b aber schon 
anlegen, damit Seiteneffekt stattfinden, oder nicht?
OP #6007735
Lesenswert?

Heiko L. schrieb:
> Moment  - mit dynamischer Initialisierung müsste er das s.b aber schon
> anlegen, damit Seiteneffekt stattfinden, oder nicht?

Richtig, jedoch in keiner definierten Reihenfolge.

In dem oben von mir geposteten Beispiel ->
1
template<typename T>
2
struct A {
3
  
4
  // ... Funktionen die io_ und t_ nutzen
5

6
  static inline boost::asio::io_context io_;
7
  static inline boost::asio::steady_timer t_{io_};
8
};

initialisiert Clang den steady_timer vor dem io_context. Weil der 
steady_timer intern aber eine von io_context bereitgestellte Klasse 
nutzt die am Heap angelegt wird, explodiert das ganze Programm noch 
während dem Startup in einem Segfault.
#6007738
Lesenswert?

Vincent H. schrieb:
> Richtig, jedoch in keiner definierten Reihenfolge.

Ja, darum geht es ja gerade. Begründung: "Wir wissen halt nicht, wo das 
Initialisiert wird und müssen deshalb halt Flags checken und ggf. 
Funktionen aufrufen. Und das in jeder TU, wo eine Variable benutzt 
wird."
Das Problem geht aber darauf zurück, dass solche Sachen wie LTO usw. 
nicht definiert sind. Jetzt wo mit Modulen Teile der Generation von 
Binärdateien definiert werden, gäbe es die Möglichkeit, diesen Mangel 
auszuräumen. Überschlage ich jedenfalls so im Kopf.
OP #6007748
Lesenswert?

Das Problem ist ähnlich, die Baustelle aber eine andere. Innerhalb einer 
.cpp Datei ist die Reihenfolge für "normale statische Variablen" schon 
definiert.

Wenn du also so ein Problem besitzt, dann könntest du dir mit "inline 
variables" auch jetzt schon helfen:
https://en.cppreference.com/w/cpp/language/inline

Oder wenn du 5:39 hast, dann hör mal kurz Jason Turner zu:
https://www.youtube.com/watch?v=m7hwL0gHuP4
#6007757
Lesenswert?

Vincent H. schrieb:
> Das Problem ist ähnlich, die Baustelle aber eine andere. Innerhalb
> einer
> .cpp Datei ist die Reihenfolge für "normale statische Variablen" schon
> definiert.
>
> Wenn du also so ein Problem besitzt, dann könntest du dir mit "inline
> variables" auch jetzt schon helfen:
> https://en.cppreference.com/w/cpp/language/inline
>
> Oder wenn du 5:39 hast, dann hört mal kurz Jason Turner zu:
> Youtube-Video "C++ Weekly - Ep 170 - C++17's `inline` Variables"

Ja, eben nicht, wie man hier liest.

Dieser Defekt der Kernsprache wurde hier diskutiert:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#270
#6007843
Lesenswert?

Vincent H. schrieb:
> Heiko L. schrieb:
>> Ja, und? Begründung noch gültig?
>
> cppreference hat imho eine ganz gute Übersicht:
> https://en.cppreference.com/w/cpp/language/initialization
>
> Da steht eigentlich recht deutlich was wann passiert.

Ja, richtig. Wer braucht schon mehr als 640kb RAM?
Sprach-Defekte sind IMHO offene Bugtickets und müssen daher gelegentlich 
einer Revision unterzogen werden.
Das steht nicht in cppref. Und auch nicht in Wikipedia.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren