Gründe gegen globale Variabeln



  • Hallo zusammen, ich höre immer wieder: "Globale Variabeln sind hässlich/unschön" oder "Das ist kein guter Stil" und solche Aussagen. Dabei kommt es mir manchmal so vor, als ob diejenigen, die das sagen, selber keine Ahnung haben und einfach einmal gehört haben, dass man das nicht macht - diesen Eindruck habe ich, weil ich noch nie wirklich eine plausible Begründung dieser Sichtweise gesehen habe.

    Dabei gibt es ja verschiedene Möglichkeiten, globale Variabeln anzulegen. Integrale Konstanten zum Beispiel kann man auf folgende Weisen erstellen:

    const int MyInteger = 42;
    enum Enum {EnumeratorA, EnumeratorB, EnumeratorC};
    #define KONSTANTE 33 // was ich ehrlich gesagt auch hässlich finde
    

    Vom Makro (das ja nicht wirklich eine globale Variable ist) einmal abgesehen, z.B. die const Type MyVar -Variante finde ich gar nicht so schlecht. Enums auch nicht - ich habe aber auch gehört, dass Enums besser als einzelne const -Variabeln wären.
    Was ich aber nicht nachvollziehen kann, wieso man als Alternative zu Klassen rät, z.B. begreife ich den Sinn von Singletons nicht ganz (und ja, ich habe den Wikipedia-Artikel gelesen). Aber auch sehr einfache Klassen werden manchmal vorgeschlagen als Alternative zu globalen Variabeln:

    class MyClass
    {
       public:
       static int MyVar;
    }
    

    Und das soll angeblich besser sein. Liegt das einfach am Anspruch, möglichst objektorientiert programmieren zu wollen? Unter "global" verstehe ich die Gegebenheit, von überall (nach der Deklaration) auf eine Variable zugreifen zu können. So eine Klasse ist jedoch so gesehen auch global, und ein Enum ebenso. Was spielt es denn für eine Rolle, ob man halt auf die Variable nicht direkt, also über eine Klasse zugreifen kann?

    Und Konstanten sind ja nicht das einzige. Generell bin ich der Ansicht, einige nicht-konstante globale Variabeln seien durchaus angebracht. Zum Beispiel solche, die von sehr vielen Funktionen verwendet werden (z.B. bei einem Grafikprogramm eine Klasse für ein Fenster). Da finde ich es blödsinnig, von main() aus eine Funktion mit 5 Parametern aufzurufen, und diese Funktion ruft dann wiederum eine andere mit 3 dieser Parameter auf.
    Und inwiefern soll extern eine Lösungsmöglichkeit darstellen? Die Variable ist dann ja immer noch global. Oder kommt es auf die Definition an?

    Also, die konkrete Frage lautet: Was spricht grundsätzlich gegen globale Variabeln? Nur dass man von überall zugreifen kann und sie dadurch aus Versehen verändert, scheint mir nicht wirklich ein überzeugendes Element, schon gar nicht bei Konstanten. Zumal bei den vielen Parameterübergaben an Funktionen sich ebenso Fehler einschleichen können - einmal abgesehen von Mehraufwand und hässlichem Code.



  • Ich schätze mal, wenn die Rede von den globalen Variablen ist, dann meint man damit keine Konstanten (gegen die hat wirklich niemand was, eher im Gegenteil, man sollte sie sogar benutzen, wo sie Sinn machen).

    Und der Grund "dass man sie aus Versehen ändert" ist wichtig. Stell dir nur mal eine Software vor, die eine Bank oder so etwas benutzt, und dann wird aus Versehen der Kontostand verändert. Das würde dich auch nicht erfreuen 😉



  • Nehmen wir einmal folgendes vereinfachtes Beispiel an. Überall, wo "hier" steht, kann die Variable verändert werden und hat nachher einen falschen Wert.

    void f2(int &b)
    {
       b = 5;
       // hier
    }
    
    void f1(int &a, int &b)
    {
       f2(b);
       a = 4;
       // hier
    }
    
    void f3()
    {
       // ok, hier nicht
    }
    
    int main()
    {
       int a, b;
       f1(a, b);
       // hier
       f3();
       // hier
    }
    

    Und wir reden von einer sehr häufig benutzten Variable, die in den meisten Funktionen benötigt bzw. verändert wird. Mir ist klar, eine selten verwendete Variable besser lokal zu deklarieren. Lohnt es sich wegen den paar wenigen Funktionen, in denen man sie nicht verändert, dafür einen starken Mehraufwand und ellenlange Parameterlisten zu haben? Oder gibt es noch andere Gründe ausser der nicht absichtlichen Veränderung (die ja wie oben gezeigt fast genau so passieren kann)?



  • Auf was für Fälle beziehst Du Dich? Ich höre die Aussage eigentlich nur, wenn sie in meinen Augen Sinn macht. Natürlich kommt es immer auf den Fall an.

    Einfaches Beispiel:
    Jemand definiert eine globale Variable in einer Übersetzungseinheit, ohne Deklaration in einem Header. Hierbei würde ich keifen, ganz einfach weil er unnötigerweise ein globales Symbol erzeugt. Wenn zwei Leute diesen Mist verzapfen, und die resultierenden Objekte zusammengelinkt werden, hagelt es Duplicate Symbols. Alternativvorschlag: anonymous namespaces oder internal linkage (static).

    Anfängerbeispiel:
    Jemand definiert global eine Zählvariable und benutzt sie in mehreren Funktionen in Schleifen. Hier brauche ich glaube ich nicht erläutern, warum man "pfui deibel!" rufen sollte. Zugegeben, das Beispiel ist extrem, kommt aber gerade bei Newbies nicht selten vor.

    C++-Beispiel:
    Wenn mehrere globale Objekte in unterschiedlichen ÜEs voneinander abhängen, muss die Initialisierungsreihenfolge beachtet werden. Das kann nur funktionieren, wenn man von globalen Objekten Abstand nimmt und auf andere Konzepte, z.B. (sog. Meyers-)Singletons ausweicht.

    Weitere Beispiele fallen mir vorerst nicht ein, aber Fälle, in denen ich - neben diesen Situationen - Globals verwenden würde, auch nicht.



  • Variablen sind veränderbar. Konstanten und Enums sind konstant , also nicht veränderbar, also keine globalen Variablen.

    Globale Variablen sind doch meistens einfach nur undefinierte Schnittstellen. Irgendeines von 10000 Objekten setzt irgendwo einen Wert in einer globalen Variable und irgendwelche anderen lesen den Wert aus und/oder verändern ihn auch wieder. Sowas ist einfach schlecht, da sollte man bessere saubere Schnittstellen und Komunikationswege definieren.

    Das Singletonpattern ist kein Ersatz für globale Variablen. Es ist eine Idee, wie man etwas lösen kann, bei dem es nur eine Instanz einer Klasse geben darf und z.B. alle mit der gleichen Instanz arbeiten müssen, damit es funktioniert (z.B. begrenzte Ressourcen verwalten, Caches... ).



  • In einem Minimalbeispiel sehen die meisten Sachen gut aus. Problematisch wird das ganze erst wenn man sich mal Maximalbeispiele anschaut, meinetwegen ein Großprojekt mit hunderten Klassen, Funktionen usw.

    Wobei die Aussage gegen globale Variablen meistens im Umfeld von OOP angesiedelt ist. Ein zentraler Aspekt des OOP ist die Data Encapsulation, wo es darum geht das die Daten einer Klasse (aka ihre Variablen) niemals direkt von "ausserhalb" der Klasse zugegriffen werden sollen. Globale Variablen stehen da im direkten Widerspruch zum Prinzip der Data Encapsulation.

    Bei einer Klasse die diesem Prinzip streng folgt kannst Du Dich auf eine Sache verlassen: Eine Variable kann nur(!) von Funktionen der selbigen verändert werden. Wenn Du Änderungen z.B. am variablentyp vornimmst musst Du also nur sicherstellen das alle FUnkltionen innerhalb der Klasse damit klarkommen. Das ist überschaubar und damit weniger Fehleranfällig. Im Falle einer Änderung mußt Du auch nur die Klasse selber gründlich testen.

    Bei einer globalen Variablen hingegen kann von überall im Sourcecode auf selbige zugegriffen werden. Überall kann bei einem großen Projekt auch mal 100k Zeilen Sourcecode und mehr bedeuten. Schlimmer noch, als einzelner Entwickler hast Du eventuell nicht einmal Zugriff auf den gesamten Sourcecode. Würdest DU in so einer Umgebung z.B. den Typ der Variablen ändern ist die Chance, daß Du damit massig Probleme erzeugst nahezu vorprogrammiert.

    Zumindest im OOP haben globale Variablen daher im Grunde nichts zu suchen. (Ausnahmen gibts immer...)

    Hinzu kommt das globalen Variablen nicht nur von überall im SOurcecode geändert werden können, sondern auch _jederzeit_. Im Falle einer Multithreading Umgebung kann es also passieren das die Variable die eine routine gerade verarbeitet sich mittnedrin verändert, weil eine andere routine aus einem andern Thread heraus diese gerade beschreibt. Was dann? Konsequenterweise müßte man also globalen Variablen auch mit Locks versehen, was garantiert viel mehr Performance kostet als eine simple Parameterübergabe...

    Jetzt versuch Dir mal vorzustellen wie man in einer solchen Umgebung sinnvolle Tests schreiben soll? Eine Funktion die keine globalen Variablen benutzt ist relativ einfach zu testen (->siehe Unit tests). Man weis welche Parameter übergeben werden (Vorbedingung) und was rauskommen muss (Nachbedingung). Wie aber willst Du eine Funktion testen wenn diese globale Variablen benutzt deren Inhalt sich ja jederzeit ändern kann? Im Grunde ein unmögliches Unterfangen, weil man das nicht als Vorbedingung definieren kann.

    Und was, wenn die Variable z.B. nur einen bestimmten Wertebereich haben darf? Solche Einschränkungen sind nicht ungewöhnlich. Im Falle einer Klasse kann man das mit dem Pattern Getter/Setter lösen so das immer sichergestellt ist das die Variable keine ungültigen Werte bekommen kann. Auf eine globale Variable aber kann man per Definition von überall her zugreifen. Wenn also irgend eine der Routine nicht sicherstellt das der vorgegebene Wertebereich eingehalten wird... bumm...



  • also ich bin noch anfänger und finde das ganze hier recht interessant. aber zum thema lange parameter liste fällt mir ein das es ja auch winapi funktionen gibt die als parameter einen pointer zu einer struktur haben wollen und so die daten übergeben werden bzw. auch so zurückgegeben werden.



  • Ich mache generell keine Unterschied zwischen Variablen und Konstanten, es gibt
    keinen sachlichen Grund dafür. Die Rechner sind heute dermaßen schnell, daß der
    Unterschied zwischen einer Variable und einem #define nicht mehr auffällt.

    Globale Variable nehme ich, wenn zB die Sprache oder eine Bildschirmauflösung
    dauerhaft für alle Funktionen verwendet wird (einmal setzen und dann verwenden).

    static für Variable die nur innerhalb eines Sourcefiles gelten (elend praktisch).

    Innerhalb einer Funktion generell für Laufvariable etc.

    Manchmal auch völlig anders, je nachdem wie das brauche und will (das entscheide
    ich und nicht der Compiler :)).



  • Okay, vielen Dank für die vielen Erläuterungen!

    So habe ich das bis jetzt noch nicht gesehen. Ich selber bin Hobbyprogrammierer und als solcher habe ich weder riesigen Sourcecode mit mehreren hundert Klassen und Funktionen noch ein striktes OOP-Design. Deshalb konnte ich auch nicht wirklich nachvollziehen, welche dramatischen Auswirkungen globale Variabeln haben.

    Bis jetzt habe ich auch eigentlich immer in nur mit einer Übersetzungseinheit und mehreren Headern gearbeitet (momentan brauche ich eigentlich auch nicht mehrere CPP-Dateien). Von da her spielt die Deklaration auch nicht so eine grosse Rolle, z.B. habe ich noch nie wirklich extern benötigt. Auch Multithreading und andere Konzepte wie Singletons benötigte ich bisher nicht. Aber ich kann mir vorstellen, dass ich mich gerade bei grösseren Projekten umstellen muss, auch was globale Variabeln betrifft.

    @ LordJaxom
    Ich habe noch ein Beispiel. Ich programmiere in meiner Freizeit ab und zu kleine Games und in meiner Grafikbibliothek (SFML) gibt es eine Fenster-Klasse, die sozusagen hinter allen Grafikaktionen steht. Das heisst, man lässt sie alles zeichnen und anzeigen, sie regelt sogar Tastatur- und Mauseingaben.
    Meiner Ansicht nach ist eine solche globale Variable gerechtfertigt, da sie fast überall benutzt wird (selbst in Logikfunktionen, z.B. für die Höhe und Breite etc.). Mein Code ist aber auch verhältnismässig klein und überblickbar, daher passiert es mir nicht so schnell, dass ich aus Versehen etwas ändere. Und im Weiteren ist es auch eine Klasse, deren Attribute nur über Funktionsschnittstellen manipuliert werden können.

    In solchen Anwendungsbereichen finde ich globale Variabeln sehr praktisch, aber ich kann nach euren Erklärungen auch gut nachvollziehen, wieso sich das (bei mir langfristig) negativ auswirken könnte.



  • Hi Nexus,

    Dein Programmierstil wird sich irgendwann herausbilden - stehe dazu.



  • Scheppertreiber schrieb:

    Hi Nexus,

    Dein Programmierstil wird sich irgendwann herausbilden - stehe dazu.

    Habe ich denn irgendwo nicht dazu gestanden? Ich erklärte ja mein Niveau und sagte auch, ich könne alles nachvollziehen und werde in Zukunft damit wohl auch klarkommen müssen. Ausserdem würde ich keine solche Frage stellen, wenn ich nicht an Veränderungen interessiert wäre. 🙄



  • Scheppertreiber schrieb:

    ...Die Rechner sind heute dermaßen schnell, daß der
    Unterschied zwischen einer Variable und einem #define nicht mehr auffällt....

    😮 😮
    äh "Schnelligkeit" ist eigentlich nie ein Thema zwischen "Variable" und "#define" ... (allein schon, weil der Compiler sowieso Konstanten "direkt einsetzen" kann).
    Viel wesentlicher ist, dass #defines
    a) "dumme Textersetzung" machen und damit zu ärgerlichen Fehlern führen können und
    b) nicht "adressierbar" sind, weswegen man sie oftmals nicht verwenden kann:

    void f(int const&);
    
    int const i1 = 2;
    #define i2 2
    
    int main() {
       f(i1); // geht
       f(i2); // geht nicht
    

    sizeof() tut's nicht und eine Menge anderer Dinge ...

    Ergo: #define da, wo man Textersetzung braucht und nicht als "Konstanten" ... schließlich hat C++ ja Konstanten dafür.

    Gruß,

    Simon2.



  • Ich weiß ja nicht, ob das Compilerspezifisch ist, aber ich glaube nicht:
    alles was du genannt hast geht.
    Ersteres, weil man als konstante Referenz durchaus auch ein Literal übergeben kann(allerdings natürlich nur, wenn sie auch konstant ist).
    Ein Zahlenliteral ist afaik auch immer vom Typ Integer und wird von sizeof auch so angesehen. Eine Gleitkommazahl immer ein Double, ein String immer ein const char* und ein Charliteral immer ein char*.


  • Mod

    Simon2 schrieb:

    f(i2); // geht nicht
    

    was geht nicht?

    Simon2 schrieb:

    sizeof() tut's nicht

    tut was nicht?

    Präprozessormakros ignorieren Scopes, werten ihre Argumente ggf. mehrfach aus (was fast immer unerwünscht ist) und sind für den Linker und in der Regel auch für den Debugger unsichtbar, was die Benutzung von Letzterem erheblich erschweren kann.
    "Schnelligkeit" ist außerhalb einer konkreten Implementation und eines konkreten Programmes ein weitgehend bedeutungsloser Begriff.



  • Hi Simon2,

    solche Probleme sollte man im Griff haben.

    Kennt man den Unterschied zwischen einem Präprozessor und einen Compiler ist
    der rest auch trivial. Mir ist nicht erklärbar, daß da ständig solche Grundsatzdiskussionen
    auftauchen.

    Ein Präprozessor hat mit der Programmiersprache überhaupt rein gar nix zu tun.

    Grüße Joe.



  • Hat jemand noch ein weiteres Argument, das gegen die globalen Variabeln spricht?
    Nicht dass mich die bisherigen zu wenig überzeugt hätten, das waren sogar ziemlich gute Argumente 😃

    Nur falls jemand noch etwas hinzufügen möchte oder es noch einen anderen wichtigen Grund gegen globale Variabeln gibt...



  • Scheppertreiber schrieb:

    ...Mir ist nicht erklärbar, daß da ständig solche Grundsatzdiskussionen auftauchen...

    Mir auch nicht ... aber da Du es mit Deinem Plädoyer für #defines getan hast, dachte ich, darauf antworten zu sollen...

    Scheppertreiber schrieb:

    ...Ein Präprozessor hat mit der Programmiersprache überhaupt rein gar nix zu tun....

    Eben.
    Und da die Sprache bereits Konstanten kennt, sehe ich keinen Grund, diese Funktionälität auf "#defines" auszulagern.

    Gruß,

    Simon2.



  • JustAnotherNoob schrieb:

    ...
    alles was du genannt hast geht.
    ...

    camper schrieb:

    Simon2 schrieb:

    f(i2); // geht nicht
    

    was geht nicht?

    Simon2 schrieb:

    sizeof() tut's nicht

    tut was nicht?
    ...

    Hupps ! 🙄

    OK, mein ursprüngliches Beispiel war auch ein anderes, das ich hier auch mal zur Frage stelle:

    void f(int const* p) { cout << static_cast<void const*>(p); }
    
    int const i1 = 2;
    #define i2 2
    
    int main() {
       f(&i1); // ??
       f(&i2); // ??
    
    // ... oder ganz erste Idee:
       int const* arr[] = { &i1, &i2 };
    

    ... und wie immer, wenn man sich in letzter Sekunde noch mal spontan umentscheidet ("das geht ja noch einfacher"), übersieht man auf die Schnelle, dass es doch nicht "dasselbe" ist.
    Spätestens, wenn man int, char, double, & Co verlässt und sich sowas zuwendet:

    struct A { A(int, string); };
    

    dürften "Konstenten via Präprozessor" schon schwerer werden, oder ?
    (OK, man könnte auf temp-Variablen ausweichen)

    Gruß,

    Simon2.


Anmelden zum Antworten