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
structS{
2
staticinlineinti{42};
3
staticinlineint*pi{&i};
4
};
5
6
template<typenameT>
7
structS{
8
staticinlineTt{42};
9
staticinlineT*pt{&t};
10
};
Oder lässt der Standard es zu, dass die Pointer zuerst initialisiert
werden?
/edit
Typo
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
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.
Wilhelm M. schrieb:> Schaut mir eher so aus, als leidest Du unter dem static initialization> fiasco.> Poste Dein komplettes Beispiel.
Nein, single translation unit.
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?
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.
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...
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?
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
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...
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.
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.
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...
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.
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.
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
structS{
3
staticinlineinta{42};
4
staticinlineintb{43};
5
};
6
7
S<void>s;
8
9
intmain(){
10
returns.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. :(
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?
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<typenameT>
2
structA{
3
4
// ... Funktionen die io_ und t_ nutzen
5
6
staticinlineboost::asio::io_contextio_;
7
staticinlineboost::asio::steady_timert_{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.
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.
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
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
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.