mikrocontroller.net

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


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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:
struct S {
  static inline int i{42};
  static inline int* pi{&i};
};

template<typename T>
struct S {
  static inline T t{42};
  static inline T* pt{&t};
};

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

/edit
Typo

: Bearbeitet durch User
Autor: mh (Gast)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Der Standard sagt http://eel.is/c++draft/class.static.data#6:
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.

Autor: mh (Gast)
Datum:

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

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Wilhelm M. (wimalopaan)
Datum:

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

Autor: DPA (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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.
#include <boost/asio.hpp>
#include <iostream>

template<typename T>
struct A {
  static void run() {
    std::thread{[] {
      t_.expires_after(std::chrono::seconds{1});
      t_.async_wait(handler);
      io_.run();
    }}
        .detach();
  }

  static void stop() {
    while (!io_.stopped())
      ;
  }

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

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

//template<typename T>
//boost::asio::io_context A<T>::io_;
//template<typename T>
//boost::asio::steady_timer A<T>::t_{A::io_};

using MyA = A<void>;

int main() {
//  volatile auto io_adr{&MyA::io_};
//  volatile auto t_adr{&MyA::t_};
  MyA::run();
  MyA::stop();
}


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


fällt folgender Assembler Code hinten raus.
25                  static inline boost::asio::steady_timer t_{io_};
000055555555b854:   cmpb    $0x0,0x26b65(%rip)        # 0x5555555823c0 
000055555555b85b:   jne     0x55555555b8a3 <__cxx_global_var_init.13(void)+83>
...
000055555555b8a4:   retq    
   
24                  static inline boost::asio::io_context io_;
                  __cxx_global_var_init.14(void):
000055555555b8b0:   push    %rbp
...
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...

Autor: Vincent H. (vinci)
Datum:

Bewertung
1 lesenswert
nicht lesenswert

Autor: Sebastian (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: nfet (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Vincent H. (vinci)
Datum:

Bewertung
1 lesenswert
nicht 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...

Autor: mh (Gast)
Datum:

Bewertung
1 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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:
execution_context::execution_context()
  : service_registry_(new boost::asio::detail::service_registry(*this))
{
}

War mir allerdings trotzdem nicht bewusst...

: Bearbeitet durch User
Autor: mh (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Brauchst du für jede Spezialisierung einen eigenen Context?

Autor: noone (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
1 lesenswert
nicht 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...

Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
1 lesenswert
nicht 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.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Verwechselt du da vielleicht grad constinit mit constexpr?
Der Bug wurde bereits assigned?


/edit
bzw. consteval...?

: Bearbeitet durch User
Autor: Wilhelm M. (wimalopaan)
Datum:

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

Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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
template<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. :(

Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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 ->
template<typename T>
struct A {
  
  // ... Funktionen die io_ und t_ nutzen

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

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.

Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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:
Youtube-Video "C++ Weekly - Ep 170 - C++17's `inline` Variables"

: Bearbeitet durch User
Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Heiko L. (zer0)
Datum:

Bewertung
0 lesenswert
nicht 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
Autor: Vincent H. (vinci)
Datum:

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

Autor: Heiko L. (zer0)
Datum:

Bewertung
-2 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.