Forum: PC-Programmierung C++ Initialisierungsreihenfolge von static inline membern


von Vincent H. (vinci)


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

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Der Standard sagt http://eel.is/c++draft/class.static.data#6:
1
11.4.8.2 Static data members
2
6 Static data members are initialized and destroyed exactly like non-local variables.
So wie ich das sehe, ist die Reihenfolge festgelegt.

von mh (Gast)


Lesenswert?

Und schon bin ich mir nicht mehr sicher. Was passiert, wenn die struct 
mehrfach deklariert wird, mit den Adressen von inline Variablen?

von Vincent H. (vinci)


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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Schaut mir eher so aus, als leidest Du unter dem static initialization 
fiasco.
Poste Dein komplettes Beispiel.

von DPA (Gast)


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.

von Vincent H. (vinci)


Lesenswert?

Wilhelm M. schrieb:
> Schaut mir eher so aus, als leidest Du unter dem static initialization
> fiasco.
> Poste Dein komplettes Beispiel.

Nein, single translation unit.

von Wilhelm M. (wimalopaan)


Lesenswert?

Vincent H. schrieb:
> Nein, single translation unit.

Ok.

Poste trotzdem Dein gesamtes Beispiel. Ich nehme an, dass die 
int-Variante korrekt läuft. Für welchen Typ instanziierst Du das 
Template?

von Vincent H. (vinci)


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

von Vincent H. (vinci)


Lesenswert?


von Sebastian (Gast)


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?

von nfet (Gast)


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

von Vincent H. (vinci)


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

von mh (Gast)


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.

von Vincent H. (vinci)


Lesenswert?

mh schrieb:
> Da ist jetzt noch zu klären, ob es dynamic initialization ist.

Ja leider. Irgendwo in der Kette an Ctor aufrufen passiert folgendes 
hier:
1
execution_context::execution_context()
2
  : service_registry_(new boost::asio::detail::service_registry(*this))
3
{
4
}

War mir allerdings trotzdem nicht bewusst...

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Brauchst du für jede Spezialisierung einen eigenen Context?

von noone (Gast)


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.

von Vincent H. (vinci)


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

von Heiko L. (zer0)


Lesenswert?

Vincent H. schrieb:
> Ich wollt
> einfach nur wissen ob dieses (imho absurde Verhalten) normal ist...

Ist dies schon Wahnsinn, so hat es doch Methode.

von Vincent H. (vinci)


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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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


Der Bug wird auch geschlossen werden, da new nicht mit delete gepaart 
ist, was für literal-typen Pflicht ist bzw. sein wird.

von Vincent H. (vinci)


Lesenswert?

Verwechselt du da vielleicht grad constinit mit constexpr?
Der Bug wurde bereits assigned?


/edit
bzw. consteval...?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Oh ja, in der Tat. Habs gerade gelesen: kein constexpr-dtor verlangt. 
Gut!

von Heiko L. (zer0)


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.

von Vincent H. (vinci)


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. :(

von Heiko L. (zer0)


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?

von Vincent H. (vinci)


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.

von Heiko L. (zer0)


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.

von Vincent H. (vinci)


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

: Bearbeitet durch User
von Heiko L. (zer0)


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

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

Heiko L. schrieb:
> 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

Ja vor 18 Jahren?
"9 Feb 2001"

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Vincent H. schrieb:
> Heiko L. schrieb:
>> 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
>
> Ja vor 18 Jahren?
> "9 Feb 2001"

Ja, und? Begründung noch gültig?

BTW wird das Thema alle paar Jahre wieder mal aufgemacht

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

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.

von Heiko L. (zer0)


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.

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

Ich versteh ehrlich gesagt nicht genau was du meinst? Wo genau besteht 
hier aktuell ein Defekt?

von Heiko L. (zer0)


Lesenswert?

Vincent H. schrieb:
> Ich versteh ehrlich gesagt nicht genau was du meinst? Wo genau
> besteht
> hier aktuell ein Defekt?

Weiß nicht.
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#362
Versteht du das? Steht unter "Defekte"

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.