static initialization order fiasco
-
Hallo allerseits,
falle ich mit folgendem Code dem static initialization order fiasco zum Opfer?
Baz.h
struct Baz { double v; }; Baz foo(double input); Baz bar(double input);Baz.cpp
#include "Baz.h" namespace { const double c1 = 42.0; const double c2 = c1 * c1; // ... viele weitere Konstanten, welche in den folgenden // Funktionen zur Verfügung stehen sollen } Baz foo(double input) { static const double x1 = 123.0; static const double x2 = x1 * x1 + 456.0; // ... viele weitere Konstanten, die nur innerhalb von foo() benötigt werden Baz baz; baz.v = input + c1 + c2 + x1 + x2; // unglaublich lange und komplizierte Berechnung, welche // alle globalen Konstanten c_n und lokalen Konstanten x_n // enthält return baz; } Baz bar(double input) { static const double y1 = 123.0; static const double y2 = y1 * y1 + 456.0; // ... viele weitere Konstanten, die nur innerhalb von bar() benötigt werden Baz baz; baz.v = input + c1 + c2 + y1 + y2; // unglaublich lange und komplizierte Berechnung, welche // alle globalen Konstanten c_n und lokalen Konstanten y_n // enthält return baz; }main1.cpp
#include "Baz.h" int main() { Baz b = foo(999.0); return 0; }main2.cpp
#include "Baz.h" Baz b = foo(999.0); int main() { return 0; }Ist das Design problematisch? Habt ihr bessere Vorschläge? main1.cpp dürfte ja unkritisch sein; wie siehts mit main2.cpp aus?
-
Die Konstanten im anonymen Namespace (
c1,c2, ...) werden alle per constant initialization initialisiert.N3337 [basic.start.init]/1 schrieb:
Constant initialization is performed:
- [...]
- if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
Und daher werden sie vor
bin main2.cpp initialisiert, da dieses Objekt dynamisch initialisiert wird.Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
Damit ist auch main2.cpp richtig.
Mach auch ggf. die Konstantenconstexpr.
-
Natürlich gilt das nur, solange tatsächlich nur konstante Ausdrücke vorkommen. In deinem Beispiel ist das der Fall. Um das zu erzwingen, qualifiziere die Konstanten nochmal mit
constexpr(wenn dein Compiler genügend C++11 unterstützt). Sollte da aber irgendwo =std::sin(5.)stehen, hast du AFAICS ein Problem.
-
Sone schrieb:
Sollte da aber irgendwo =
std::sin(5.)stehen, hast du AFAICS ein Problem.Nicht, wenn sin ein Intrinsic ist.
Wird wohl mit jedem Compiler gehen, aber verlassen kann man sich darauf nicht.
-
Danke für die Anworten. Also wenn ich in den Assembler-Output schaue, dann werden trotz voller Optimierungen nicht sämtliche Konstanten im unnamed namespace constant initialized. Bei manchen steht
c5$initializer$ DQ FLAT:void __cdecl `anonymous namespace'::`dynamic initializer for 'c5''(void)und dann später halt die entsprechenden Anweisungen zum berechnen der Werte. Das wundert mich auch sehr, dass hier die constant propagation nicht so funktioniert, wie ich mir das gedacht hatte. VS2012 scheint die double-Konstanten so ca. 3 Ausdrücke weiterzupropagieren, die dann auch alle schön brav zur Compilezeit berechnet werden, aber nachfolgende (immer noch
const) Konstanten werden nicht mehr zur Compilezeit bestimmt. Und das alles, obwohl ich zur Berechnung der Konstanten nur +-*/ verwende (also nix mit Sinus o.ä.).C++11/constexpr steht mir leider nicht zur Verfügung.
-
Hm, ist meine Frage so schwierig, dass keiner sich mehr traut was zu schreiben, oder ist die Antwort so offensichtlich?

Wäre für weitere Kommentare sehr dankbar!
Grüße
-
Zeig mal den Code zu den Initialisierungen, die nicht optimiert werden.
-
Gleitkommaliterale sind in C++03 die einzigen konstanten Ausdrücke mit Gleitkommatyp.
Entsprechend ist die Initialisierung von c2/x2/y2 in C++03 dynamisch.
Natürlich könnte ein Compiler die Initialisierung trotzdem statisch durchführen. Traditionell wurde das aber nichtz gemacht, weil es unterschiedliche Implementationen für Gleitkommaarithmetik gibt, und der Compiler diese (ggf. unterschiedlich je nach Zielplattform) implementieren müsste.In C++11 ist das anders, dort wird aber eben auch nicht ausdrücklich verlangt (allerdings nach Möglichkeit empfohlen), dass das Ergebnis einer Berechnung beim Compilieren mit dem einer Berechnung erst beim Programmablauf exakt übereinstimmen muss.
-
SeppJ schrieb:
Zeig mal den Code zu den Initialisierungen, die nicht optimiert werden.
namespace { const double c1 = 123.0; const double c2 = 456.0; const double c3 = c1 * (c2 - 1.0) / c2; const double c4 = ((c1 * c1) - (c3 * c3)) / (c1 * c1); } int main() { return c4; }c3schafft er noch,c4nicht mehr:; Listing generated by Microsoft (R) Optimizing Compiler Version 17.00.60315.1 include listing.inc INCLUDELIB OLDNAMES c1 DQ 0405ec00000000000r ; 123 c2 DQ 0407c800000000000r ; 456 CONST ENDS PUBLIC main PUBLIC __real@40cd8c8000000000 EXTRN _fltused:DWORD c4 DQ 01H DUP () _BSS ENDS CRT$XCU SEGMENT c4$initializer$ DQ FLAT:void __cdecl `anonymous namespace'::`dynamic initializer for 'c4''(void) CRT$XCU ENDS ; COMDAT __real@40cd8c8000000000 CONST SEGMENT __real@40cd8c8000000000 DQ 040cd8c8000000000r ; 15129 c3 DQ 0405eaebca1af286cr ; 122.73 _DATA ENDS ; Function compile flags: /Ogtpy ; File c:\dev\test\test.cpp ; COMDAT void __cdecl `anonymous namespace'::`dynamic initializer for 'c4''(void) text$yc SEGMENT void __cdecl `anonymous namespace'::`dynamic initializer for 'c4''(void) PROC ; `anonymous namespace'::`dynamic initializer for 'c4'', COMDAT ; 6 : const double c4 = ((c1 * c1) - (c3 * c3)) / (c1 * c1); movsdx xmm2, QWORD PTR c3 movsdx xmm1, QWORD PTR __real@40cd8c8000000000 mulsd xmm2, xmm2 subsd xmm1, xmm2 divsd xmm1, QWORD PTR __real@40cd8c8000000000 movsdx QWORD PTR c4, xmm1 ret 0 void __cdecl `anonymous namespace'::`dynamic initializer for 'c4''(void) ENDP ; `anonymous namespace'::`dynamic initializer for 'c4'' text$yc ENDS ; Function compile flags: /Ogtpy ; File c:\dev\test\test.cpp ; COMDAT main _TEXT SEGMENT main PROC ; COMDAT ; 11 : return c4; cvttsd2si eax, QWORD PTR c4 ; 12 : } ret 0 main ENDP _TEXT ENDS ENDcamper schrieb:
Gleitkommaliterale sind in C++03 die einzigen konstanten Ausdrücke mit Gleitkommatyp.
Entsprechend ist die Initialisierung von c2/x2/y2 in C++03 dynamisch.
Natürlich könnte ein Compiler die Initialisierung trotzdem statisch durchführen. Traditionell wurde das aber nichtz gemacht, weil es unterschiedliche Implementationen für Gleitkommaarithmetik gibt, und der Compiler diese (ggf. unterschiedlich je nach Zielplattform) implementieren müsste.In C++11 ist das anders, dort wird aber eben auch nicht ausdrücklich verlangt (allerdings nach Möglichkeit empfohlen), dass das Ergebnis einer Berechnung beim Compilieren mit dem einer Berechnung erst beim Programmablauf exakt übereinstimmen muss.
Okay, das erklärt einiges. Und was heißt das jetzt bezogen auf meine ursprüngliche Frage? Habe ich also in der Tat bei main2.cpp ein Problem? Oder gibts hier sowas wie "beim ersten Aufruf einer Funktion aus einer Übersetzungseinheit sind alle statischen Variablen dieser Übersetzungseinheit initialisiert"? Ich meine, ich hätte im C++-Standard mal irgendwas von Initialisierung statischer Variablen bevor erster ODR-Nutzung einer Funktion in derselben Übersetzungseinheit gelesen. Habe es aber ehrlich gesagt nicht wirklich geblickt.
Was wäre denn ein gutes alternatives Design (C++03), um einer Übersetzungseinheit eine gewisse Menge (ca. 10-20) von Konstanten zur Verfügung zu stellen, welche von mehreren Funktionen dieser Übersetzungseinheit genutzt wird?
-
Bloops schrieb:
Und was heißt das jetzt bezogen auf meine ursprüngliche Frage? Habe ich also in der Tat bei main2.cpp ein Problem?
ja.
Bloops schrieb:
Oder gibts hier sowas wie "beim ersten Aufruf einer Funktion aus einer Übersetzungseinheit sind alle statischen Variablen dieser Übersetzungseinheit initialisiert"? Ich meine, ich hätte im C++-Standard mal irgendwas von Initialisierung statischer Variablen bevor erster ODR-Nutzung einer Funktion in derselben Übersetzungseinheit gelesen.
Sofern main bereits betreten wurde, ist das garantiert.
Bloops schrieb:
Was wäre denn ein gutes alternatives Design (C++03), um einer Übersetzungseinheit eine gewisse Menge (ca. 10-20) von Konstanten zur Verfügung zu stellen, welche von mehreren Funktionen dieser Übersetzungseinheit genutzt wird?
Spricht etwas dagegen, die Konstanten einfach in einem Header zu definieren (und auf external linkage zu verzichten)?
-
camper schrieb:
Spricht etwas dagegen, die Konstanten einfach in einem Header zu definieren (und auf external linkage zu verzichten)?
Meinst du mit
static const double ...? Hab ich dann nicht das gleiche Problem? Dann "sehen" die einzelnen Übesetzungseinheiten doch immer noch verschiedene Konstanten - oder meinst du was anderes?Die Idee war halt, die (Berechnung der) Konstanten in der Bibliothek komplett wegzukapseln. Ich könnte natürlich die Konstanten auch separat vorberechnen und wirklich nur die Gleitkommaliterale dann in C++ verwenden. Würde mich das dann vor der Initialisierungsproblematik bewahren? Wäre aber halt nicht so elegant.