2 Stimmen

Wie man Abhängigkeiten zwischen globalen Variablen findet und löst

Ich habe eine Anwendung, die [leider] ziemlich viele globale Variablen enthält. Ein kürzlicher Absturz beim Starten führte mich zu folgender Konstruktion:

FILE1.CPP:
ClassX globalVariableX;

FILE2.CPP
ClassY globalVariableY;

Leider verwendet der Konstruktor der Klasse Y Code, in dem globalVariableX verwendet wird.

Bis jetzt ging alles gut, weil (zufällig) DATEI1.OBJ vor DATEI2.OBJ eingebunden wurde, was bedeutet, dass globalVariableX vor globalVariableY instanziiert wird.

Letzte Woche führte eine völlig unzusammenhängende Änderung in anderen Dateien dazu, dass der Linker DATEI2.OBJ vor DATEI1.OBJ einbindet. Jetzt wird globalVariableY zuerst instanziiert, sein Konstrukteur verweist indirekt auf globalVariableX und stürzt ab, weil globalVariableX noch nicht instanziiert wurde.

Ich weiß, ich sollte alle globalen Variablen so weit wie möglich loswerden (bitte fangen Sie keine Diskussion darüber an).

Aber gibt es Werkzeuge, die mir helfen können, Abhängigkeiten zwischen globalen Variablen zu erkennen?

Oder gibt es irgendwelche Tricks, die ich verwenden kann, um zur Laufzeit zu sehen, ob es Abhängigkeiten gibt, die ich loswerden sollte (ich dachte an die Einführung einer Basisklasse für die globalen Variablen, in denen ich die Konstruktion von globalen Variablen protokollieren könnte, aber das ist wahrscheinlich ziemlich viel Arbeit). Irgendwelche anderen Vorschläge?

EDITAR: Alle Ihre Antworten sind sehr gute Vorschläge, wie man diese unangenehmen Probleme vermeiden kann. Aber ich war eigentlich auf der Suche nach einer Möglichkeit, diese Abhängigkeiten zu finden und nicht alle globalen Variablen zu entfernen (oder durch eine andere Konstruktion zu ersetzen). Irgendwelche Ideen für Tools, die diese Abhängigkeiten finden?

2voto

Matthieu M. Punkte 266317

Das Übliche Lösung ist es, sich auf die Initialisierung bei der ersten Verwendung zu verlassen.

In C++ können Sie local static (in Funktionen) verwenden, um dieses Verhalten zu erreichen. In C++0x (aber bereits in den wichtigsten Compilern implementiert) ist dies sogar garantiert thread-sicher (zumindest die Initialisierung).

ClassX& GetClassX() {
  static ClassX X; return X;
}

ClassY& GetClassY() {
  static ClassY Y; return Y;
}

Es gibt allerdings noch ein Problem: Ein solches Schema erkennt keine zyklischen Verweise. Es könnte einer verrückt gewordenen Rekursion ähneln und somit Ihren Stack zerstören ;)

1voto

Nick Punkte 23904

Schauen Sie hier nach: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14

[10.14] Was ist das "Fiasko der statischen Initialisierungsreihenfolge"?

Ein subtiler Weg, Ihr Programm zum Absturz zu bringen.

Das Fiasko der statischen Initialisierungsreihenfolge ist ein sehr subtiler und gemeinhin missverstandener missverstandener Aspekt von C++. Leider ist es sehr schwer zu erkennen - die Fehler treten oft auf, bevor main() beginnt.

Kurz gesagt, nehmen wir an, Sie haben zwei statische Objekte x y y die bestehen in separaten Quelldateien, zum Beispiel x.cpp y y.cpp . Nehmen wir weiter an, dass die Initialisierung für die y Objekt (typischerweise die y Konstruktor des Objekts) ruft eine Methode des Objekts x Objekt.

Das war's. So einfach ist das.

Die Tragödie ist, dass Sie eine 50%-50%ige Chance hat, zu sterben. Wenn die Kompilierungs Einheit für x.cpp bekommt zufällig zuerst initialisiert wird, ist alles in Ordnung. Aber wenn die Kompiliereinheit für y.cpp bekommen. zuerst initialisiert, dann y 's Initialisierung wird ausgeführt, bevor x 's Initialisierung, und Sie sind erledigt. Z.B., y Konstruktor könnte eine Methode auf dem x Objekt, doch die x object hasn't yet been constructed.

Ich habe gehört, dass sie unten in der McDonalds. Genießen Sie Ihren neuen Job Burger braten.

Wenn Sie denken, dass es "aufregend" ist, Russisches Roulette zu spielen Russisches Roulette mit scharfen Kugeln in der Hälfte der Kammern zu spielen, können Sie hier aufhören zu lesen. Wenn Sie hingegen Ihre Überlebenschancen verbessern wollen Überlebenschancen verbessern wollen, indem Sie Katastrophen mit systematisch vorbeugen, wollen Sie wahrscheinlich die nächste FAQ lesen.

Hinweis: Die statische Initialisierungsreihenfolge Fiasko kann in einigen Fällen auch für für eingebaute/eigenständige Typen gelten.

[10.15] Wie verhindere ich das "Fiasko der statischen Initialisierungsreihenfolge"?

Verwenden Sie die Redewendung "construct on first use". Idiom, was einfach bedeutet, dass Sie Ihre statisches Objekt in eine Funktion zu verpacken.

Nehmen wir zum Beispiel an, Sie haben zwei Klassen, Fred und Barney. Es gibt eine globales Fred-Objekt namens x, und ein globales Barney-Objekt namens y. Der Konstruktor von Barney ruft die Funktion goBowling()-Methode für das Objekt x auf. Die Datei x.cpp definiert das x-Objekt:

 // File x.cpp
 #include "Fred.h"
 Fred x;

Die Datei y.cpp definiert das y-Objekt:

 // File y.cpp
 #include "Barney.h"
 Barney y;

Der Vollständigkeit halber: Barney Konstruktor etwa so aussehen dies:

 // File Barney.cpp
 #include "Barney.h"

 Barney::Barney()
 {
   ...
   x.goBowling();
   ...
 }

Wie oben beschrieben, ist die Katastrophe tritt ein, wenn y vor x gebaut wird, was in 50% der Fälle passiert, da sie in verschiedenen Quelldateien sind.

Hierfür gibt es viele Lösungen Problem, aber eine sehr einfache und komplett portable Lösung ist es, das das globale Fred-Objekt, x, durch eine globale Funktion, x(), zu ersetzen, die die das Fred-Objekt als Referenz zurückgibt.

 // File x.cpp

 #include "Fred.h"

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 }

Da statische lokale Objekte das erste Mal konstruiert werden, wenn die Kontrolle (nur) über ihre Deklaration fließt, wird die obige Anweisung new Fred() nur einmal: das erste Mal, wenn x() aufgerufen wird. Jeder nachfolgende Aufruf wird das gleiche Fred-Objekt zurück (das auf das ans zeigt). Alles, was Sie dann tun, ist Ihre Verwendungen von x in x() zu ändern:

 // File Barney.cpp
 #include "Barney.h"

 Barney::Barney()
 {
   ...
   x().goBowling();
   ...
 }

Dies wird als "Construct On First" bezeichnet. Use Idiom genannt, weil es genau das tut: Das globale Fred-Objekt wird bei seiner ersten Verwendung.

Der Nachteil dieses Ansatzes ist, dass dass das Fred-Objekt nie zerstört wird. Es gibt eine andere Technik, die dieses Problem löst, aber sie muss mit Vorsicht verwendet werden, da sie die Möglichkeit eines weiteren (ebenso unangenehmen) Problem.

Hinweis: Die statische Initialisierungsreihenfolge Fiasko kann in einigen Fällen auch für für eingebaute/eigenständige Typen gelten.

(Aus den C++ FAQ, http://www.parashift.com/c++-faq/ )

1voto

stefaanv Punkte 13390

Vielleicht möchten Sie globale Variablen ganz vermeiden, indem Sie einen Application Builder verwenden, in dem Ihre Variablen konstruiert und an abhängige Variablen übergeben werden, um die Abhängigkeiten deutlich zu machen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X