Fiasko der statischen Initialisierungsreihenfolge

Lesezeit: 4 Minuten

Fiasko der statischen Initialisierungsreihenfolge
Glücklich Mittal

Ich habe in einem Buch über SIOF gelesen und es gab ein Beispiel:

//file1.cpp
extern int y;
int x=y+1;

//file2.cpp
extern int x;
int y=x+1;  

Nun ist meine Frage:
Werden im obigen Code folgende Dinge passieren?

  1. Beim Kompilieren von file1.cpp lässt der Compiler y unverändert, dh weist ihm keinen Speicher zu.
  2. Der Compiler weist Speicher für x zu, initialisiert ihn jedoch nicht.
  3. Beim Kompilieren von file2.cpp lässt der Compiler x unverändert, dh weist ihm keinen Speicher zu.
  4. Der Compiler weist Speicherplatz für y zu, initialisiert ihn jedoch nicht.
  5. Während Sie file1.o und file2.o verknüpfen, lassen Sie nun file2.o zuerst initialisieren, also jetzt:
    Erhält x den Anfangswert 0? oder wird nicht initialisiert?

Die Initialisierungsschritte sind in 3.6.2 “Initialisierung nicht lokaler Objekte” des C++-Standards angegeben:

Schritt 1: x und y werden null-initialisiert, bevor irgendeine andere Initialisierung stattfindet.

Schritt 2: x oder y dynamisch initialisiert wird – welches durch den Standard nicht spezifiziert ist. Diese Variable erhält den Wert 1 da die andere Variable mit Null initialisiert wurde.

Schritt 3: Die andere Variable wird dynamisch initialisiert und erhält den Wert 2.

  • Danke.

    – Glücklicher Mittal

    14. Juni 2010 um 7:48 Uhr

  • @Happy Mittal: Sie können es nicht sagen und daher kann der Compiler wählen. Es könnte sogar zur Programmladezeit sein.

    – MSalter

    14. Juni 2010 um 11:35 Uhr

SIOF ist in hohem Maße ein Laufzeitartefakt, der Compiler und der Linker haben nicht viel damit zu tun. Betrachten Sie die Funktion atexit(), sie registriert Funktionen, die beim Programmende aufgerufen werden sollen. Viele CRT-Implementierungen haben etwas Ähnliches für die Programminitialisierung, nennen wir es atinit().

Das Initialisieren dieser globalen Variablen erfordert das Ausführen von Code, der Wert kann nicht vom Compiler bestimmt werden. Der Compiler generiert also Maschinencodeschnipsel, die den Ausdruck ausführen und den Wert zuweisen. Diese Schnipsel müssen ausgeführt werden, bevor main() ausgeführt wird.

Hier kommt atinit() ins Spiel. Eine übliche CRT-Implementierung durchläuft eine Liste von atinit-Funktionszeigern und führt die Initialisierungs-Snippets der Reihe nach aus. Das Problem ist die Reihenfolge, in der die Funktionen in der atinit()-Liste registriert sind. Während atexit() eine gut definierte LIFO-Reihenfolge hat und diese implizit durch die Reihenfolge bestimmt wird, in der der Code atexit() aufruft, ist dies bei atinit-Funktionen nicht der Fall. Die Sprachspezifikation erfordert keine Reihenfolge, es gibt nichts, was Sie in Ihrem Code tun könnten, um eine Reihenfolge anzugeben. SIOF ist das Ergebnis.

Eine mögliche Implementierung ist der Compiler, der Funktionszeiger in einem separaten Abschnitt ausgibt. Der Linker führt sie zusammen und erzeugt die atinit-Liste. Wenn Ihr Compiler dies tut, wird die Initialisierungsreihenfolge durch die Reihenfolge bestimmt, in der Sie die Objektdateien verknüpfen. Schauen Sie sich die Map-Datei an, Sie sollten den atinit-Abschnitt sehen, wenn Ihr Compiler dies tut. Es wird nicht atinit heißen, aber eine Art Name mit “init” ist wahrscheinlich. Ein Blick auf den CRT-Quellcode, der main() aufruft, sollte ebenfalls einen Einblick geben.

Es ist Compiler-abhängig und kann Laufzeit-abhängig sein. Ein Compiler kann entscheiden, statische Variablen träge zu initialisieren, wenn auf die erste Variable in einer Datei zugegriffen wird, oder wenn auf jede Variable zugegriffen wird. Andernfalls werden beim Start alle statischen Variablen nach Datei initialisiert, wobei die Reihenfolge normalerweise von der Verknüpfungsreihenfolge der Dateien abhängt. Die Dateireihenfolge kann sich aufgrund von Abhängigkeiten oder anderen Compiler-abhängigen Einflüssen ändern.

Statische Variablen werden normalerweise auf Null initialisiert, es sei denn, sie haben einen konstanten Initialisierer. Auch dies ist Compiler-abhängig. Eine dieser Variablen wird also wahrscheinlich Null sein, wenn die andere initialisiert wird. Da jedoch beide Initialisierer haben, könnten einige Compiler die Werte undefiniert lassen.

Ich denke, das wahrscheinlichste Szenario wäre:

  1. Den Variablen wird Speicherplatz zugewiesen, und beide haben den Wert 0.
  2. Eine Variable, sagen wir x, wird initialisiert und auf den Wert 1 gesetzt.
  3. Der andere, sagen wir y, wird initialisiert und auf den Wert 2 gesetzt.

Du könntest es immer laufen lassen und sehen. Es könnte sein, dass einige Compiler Code generieren, der in eine Endlosschleife geht.

  • Aber mein Zweifel ist, dass, wenn der Compiler keinen Speicher für extern int y; zuweist, wie er ihn dann auf Null initialisieren kann? Oder wenn y auf Null initialisiert wird? Zur Kompilierzeit oder zur Linkzeit? Gibt es Regeln, welche Variable beim Initialisieren bekommt?

    – Glücklicher Mittal

    14. Juni 2010 um 7:15 Uhr

  • Können Sie bitte Schritt für Schritt erklären, was beim Kompilieren und Linken passieren würde/kann?

    – Glücklicher Mittal

    14. Juni 2010 um 7:23 Uhr

Der springende Punkt (und der Grund, warum es ein „Fiasko“ genannt wird) ist, dass es unmöglich ist, es mit Gewissheit zu sagen was wird in einem solchen Fall passieren. Im Wesentlichen fragen Sie nach etwas Unmöglichem (dass zwei Variablen jeweils größer als die andere sind). Da sie das nicht können, ist fraglich, was sie tun werden – sie könnten 0/1 oder 1/0 oder 1/2 oder 2/1 oder möglicherweise (im besten Fall) nur einen Fehler produzieren Botschaft.

987270cookie-checkFiasko der statischen Initialisierungsreihenfolge

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy