it-swarm.com.de

Reihenfolge der Initialisierung der statischen Variablen

C++ garantiert, dass Variablen in einer Kompilierungseinheit (.cpp-Datei) in der Reihenfolge der Deklaration initialisiert werden. Für die Anzahl der Kompilationseinheiten funktioniert diese Regel für jede Einheit separat (ich meine statische Variablen außerhalb von Klassen).

Die Reihenfolge der Initialisierung von Variablen ist jedoch für verschiedene Übersetzungseinheiten undefiniert.

Wo kann ich einige Erklärungen zu dieser Reihenfolge für gcc und MSVC finden? ?

56
Dmitry Khalatov

Wie Sie sagen, ist die Reihenfolge in verschiedenen Übersetzungseinheiten undefiniert.

Innerhalb derselben Übersetzungseinheit ist die Reihenfolge genau definiert: Die gleiche Reihenfolge wie bei der Definition.

Dies liegt daran, dass dies nicht auf der Sprachebene, sondern auf der Linker-Ebene gelöst wird. Sie müssen sich also unbedingt die Linker-Dokumentation ansehen. Obwohl ich wirklich bezweifle, wird dies auf nützliche Weise helfen.

Für gcc: Check out ld

Ich habe festgestellt, dass selbst das Ändern der Reihenfolge der verknüpften Objektdateien die Initialisierungsreihenfolge ändern kann. Sie müssen sich also nicht nur um Ihren Linker kümmern, sondern auch darüber, wie der Linker von Ihrem Build-System aufgerufen wird. Selbst der Versuch, das Problem zu lösen, ist praktisch ein Nichtstarter.

Dies ist im Allgemeinen nur ein Problem bei der Initialisierung von Global, die sich während ihrer eigenen Initialisierung aufeinander beziehen (betrifft also nur Objekte mit Konstruktoren).

Es gibt Techniken, um das Problem zu umgehen.

  • Faule Initialisierung.
  • Schwarz Counter
  • Fügen Sie alle komplexen globalen Variablen in dieselbe Übersetzungseinheit ein.
67
Martin York

Ich erwarte, dass die Konstruktorreihenfolge zwischen Modulen hauptsächlich davon abhängt, in welcher Reihenfolge Sie die Objekte an den Linker übergeben.

GCC lässt Sie jedoch verwenden Sie init_priority, um die Reihenfolge explizit anzugeben für globale Hersteller:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

gibt 'ABC' wie erwartet aus, aber

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

gibt 'BAC' aus.

17
Mike F

Da Sie bereits wissen, dass Sie sich nicht auf diese Informationen verlassen sollten, es sei denn, dies ist absolut notwendig. Meine allgemeine Beobachtung über verschiedene Toolchains (MSVC, gcc/ld, clang/llvm usw.) ist, dass die Reihenfolge, in der Ihre Objektdateien an den Linker übergeben werden, die Reihenfolge ist, in der sie initialisiert werden.

Es gibt Ausnahmen, und ich beanspruche nicht alle, aber hier sind die, denen ich selbst begegnet bin:

1) GCC-Versionen vor 4.7 werden tatsächlich in umgekehrter Reihenfolge der Verbindungslinie initialisiert. Dieses Ticket in GCC war der Zeitpunkt, an dem die Änderung stattfand, und es wurden viele Programme beschädigt, die von der Initialisierungsreihenfolge abhingen (einschließlich meiner!).

2) In GCC und Clang kann die Verwendung von Priorität der Konstruktorfunktion die Initialisierungsreihenfolge ändern. Beachten Sie, dass dies nur für Funktionen gilt, die als "Konstruktoren" deklariert sind (d. H. Sie sollten so ausgeführt werden, wie es ein globaler Objektkonstruktor tun würde). Ich habe versucht, Prioritäten wie diese zu verwenden, und festgestellt, dass selbst mit der höchsten Priorität einer Konstruktorfunktion alle Konstruktoren ohne Priorität (z. B. normale globale Objekte, Konstruktorfunktionen ohne Priorität) initialisiert werden first. Mit anderen Worten, die Priorität ist nur relativ zu anderen Funktionen mit Prioritäten, aber die echten Bürger erster Klasse sind diejenigen ohne Priorität. Um es noch schlimmer zu machen, ist diese Regel in GCC vor 4.7 aufgrund von Punkt (1) genau umgekehrt.

3) Unter Windows gibt es eine sehr übersichtliche und nützliche DLL-Einstiegspunktfunktion namens DllMain () , die, falls definiert, mit dem Parameter "fdwReason" gleich DLL_PROCESS_ATTACH direkt danach ausgeführt wird Alle globalen Daten wurden initialisiert und vorher die konsumierende Anwendung hat die Möglichkeit, alle Funktionen der DLL aufzurufen. Dies ist in einigen Fällen äußerst nützlich und es gibt absolut ist nicht analoges Verhalten zu diesem auf anderen Plattformen mit GCC oder Clang mit C oder C++. Am ehesten finden Sie eine Konstruktorfunktion mit Priorität (siehe Punkt (2) oben), was absolut nicht dasselbe ist und für viele Anwendungsfälle, für die DllMain () funktioniert, nicht funktioniert.

4) Wenn Sie CMake verwenden, um Ihre Build-Systeme zu generieren, wie ich es oft tue, habe ich festgestellt, dass die Reihenfolge der Eingabequelldateien die Reihenfolge der resultierenden Objektdateien ist, die dem Linker übergeben werden. Oftmals verlinkt Ihre Anwendung/DLL jedoch auch andere Bibliotheken. In diesem Fall befinden sich diese Bibliotheken in der Linkzeile after Ihrer Eingabequelldateien. Wenn Sie eines Ihrer globalen Objekte als sehr erstes initialisieren möchten, haben Sie Glück und können die Quelldatei, die dieses Objekt enthält, als erstes in die Liste aufnehmen von Quelldateien. Wenn Sie jedoch möchten, dass einer der allerletzte zum Initialisieren ist (wodurch das Verhalten von DllMain () effektiv repliziert werden kann!), Können Sie add_library () mit dieser einen Quelle aufrufen Datei, um eine statische Bibliothek zu erstellen, und fügen Sie die resultierende statische Bibliothek als die allerletzte Linkabhängigkeit in Ihrem Aufruf target_link_libraries () für Ihre Anwendung/DLL hinzu. Seien Sie vorsichtig, dass Ihr globales Objekt in diesem Fall optimiert wird, und Sie können das Flag - gesamtes Archiv verwenden, um den Linker zu zwingen, nicht verwendete Symbole für diese bestimmte kleine Archivdatei nicht zu entfernen.

Closing Tip

Übergeben Sie --print-map an ld linker und grep für .init_array (oder in GCC vor 4.7 grep für .ctors), um die resultierende Initialisierungsreihenfolge Ihrer verknüpften Anwendung/Shared Library zu kennen. Jeder globale Konstruktor wird in der Reihenfolge gedruckt, in der er initialisiert wird. Beachten Sie, dass die Reihenfolge in GCC vor 4.7 umgekehrt ist (siehe Punkt (1) oben).

Der motivierende Faktor für das Schreiben dieser Antwort ist, dass ich diese Informationen kennen musste, keine andere Wahl hatte, als mich auf die Initialisierungsreihenfolge zu verlassen, und nur spärliche Teile dieser Informationen in anderen SO Posts und im Internet fand Das meiste davon wurde durch viel Experimentieren gelernt, und ich hoffe, dass dies einigen Leuten die Zeit spart, dies zu tun!

13
Nicholas Smith

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 - dieser Link bewegt sich. Dieses Eins ist stabiler, aber Sie müssen sich danach umsehen.

edit: osgx lieferte ein besseres link .

4
Ray Tayek

Zusätzlich zu Martins Kommentaren, die aus einem C-Hintergrund stammen, denke ich immer an statische Variablen als Teil des Programms, das ausführbar ist, Speicherplatz im Datensegment einschließt und zugeteilt wird. Man kann sich also vorstellen, dass statische Variablen beim Laden des Programms initialisiert werden, bevor Code ausgeführt wird. Die genaue Reihenfolge, in der dies geschieht, kann durch Betrachten des Datensegments der vom Linker ausgegebenen Kartendatei ermittelt werden, die Initialisierung ist jedoch für die meisten Zwecke gleichzeitig.

Bearbeiten: Abhängig von der Konstruktionsreihenfolge von statischen Objekten ist dies wahrscheinlich nicht portabel und sollte wahrscheinlich vermieden werden.

1
SmacL

Wenn Sie wirklich die endgültige Reihenfolge erfahren möchten, empfiehlt es sich, eine Klasse zu erstellen, deren Konstruktor den aktuellen Zeitstempel protokolliert und in jeder Ihrer cpp-Dateien mehrere statische Instanzen der Klasse erstellt, damit Sie die endgültige Reihenfolge der Initialisierung kennen. Stellen Sie sicher, dass Sie ein wenig zeitaufwendige Operation im Konstruktor vornehmen, damit Sie nicht für jede Datei denselben Zeitstempel erhalten.

0
Darien Pardinas