Include guards



  • Datei a.cpp:

    #include "share.h"
    int main(){return 0;}
    

    Datei b.cpp:

    #include "share.h"
    

    Datei share.h:

    #ifndef SHARE_H
    #define SHARE_H
    int i;
    #endif
    

    Dann wird das Ganze mit
    gcc a.c -c -o a.o
    gcc b.c -c -o b.o
    gcc a.o b.o -o ab.exe
    kompiliert und es ergibt sich wie eigentlich zu erwarten ein Linkerfehler:
    b.o(.bss+0x0):b.cpp: multiple definition of `i'
    a.o(.bss+0x0):a.cpp: first defined here

    Wie benutze ich die include guards richtig um sowohl in a.cpp als auch in b.cpp auf dieselbe Variable i zugreifen zu können?

    Muss ich i in einer Datei definieren und dann extern int i in die Headerdatei schreiben oder geht das auch irgendwie eleganter?



  • Das Problem liegt nicht bei den Include Guards, sondern bei dem Linker.

    Die Variable i besitzt externe Bindung, sollte also nur einmal im ganzen Programm vorkommen (nicht in jeder Übersetzungseinheit). Jedoch schreibst du gleich die Definition in den Header, wodurch wegen der mehrfachen Verwendung des Headers mehrere Definitionen der gleichen Variable vorhanden sind.

    Um das Problem zu umgehen, kannst du extern verwenden, um in den Header nur eine Deklaration zu platzieren. Die Definition findet dann in einer der Übersetzungseinheiten statt.

    // share.h - Deklaration
    extern int n;
    
    // b.cpp - Definition
    int n;
    

    Allerdings solltest du dir überlegen, ob du wirklich globale Variablen brauchst. In den meisten Fällen gibt es bessere Alternativen.



  • Nach etwas Überlegung denke ich es ist am sinnvollsten eine share.cpp zu basteln wo i drin ist.

    Datei share.cpp:

    int i;
    

    Datei share.h:

    #ifndef SHARE_H
    #define SHARE_H
    extern int i;
    #endif
    

    Der Rest bleibt so. Damit lässt sich i als gemeinsame Variable in a.cpp und b.cpp verwenden, ohne dass i doppelt definiert wird. Interessanterweise kann das oben genannte Beispiel mit einem C-Compiler fehlerfrei kompiliert werden.



  • nwp2 schrieb:

    Nach etwas Überlegung denke ich es ist am sinnvollsten eine share.cpp zu basteln wo i drin ist.

    Wenn du noch etwas länger überlegst, findest du vielleicht heraus, dass es doch bessere Alternativen als globale Variablen gibt. 😉

    nwp2 schrieb:

    Interessanterweise kann das oben genannte Beispiel mit einem C-Compiler fehlerfrei kompiliert werden.

    Der Code ist C (somit aber auch C++). Oder was meinst du?



  • Man kann versuchen alles so zu schreiben, dass man alle Variablen lokal hat und die immer nur per Parameter oder Returnwert übermittelt.
    Blöderweise sind das alles C++-Klassen, also muss das Objekt bekannt sein. Zur Not kann man ein GetI und SetI basteln, aber damit die funktionieren muss i eh wieder global sein, es sei denn man macht Bösartigkeiten mit statischen lokalen Variablen.

    Der Code vom ersten Post, wo i doppelt definiert ist, lässt sich mit einem C-Compiler übersetzen, mit einem C++-Compiler nicht. Soweit ich das überblicke sind das dann aber nicht dieselben Variablen sondern zwei verschiedene.



  • nwp2 schrieb:

    Man kann versuchen alles so zu schreiben, dass man alle Variablen lokal hat und die immer nur per Parameter oder Returnwert übermittelt.
    Blöderweise sind das alles C++-Klassen, also muss das Objekt bekannt sein. Zur Not kann man ein GetI und SetI basteln, aber damit die funktionieren muss i eh wieder global sein, es sei denn man macht Bösartigkeiten mit statischen lokalen Variablen.

    Der Code vom ersten Post, wo i doppelt definiert ist, lässt sich mit einem C-Compiler übersetzen, mit einem C++-Compiler nicht. Soweit ich das überblicke sind das dann aber nicht dieselben Variablen sondern zwei verschiedene.

    Was für Bösartigkeiten?

    class MyClass
    {
    public:
       MyClass() : m_i(0) {}
       void setVar(int i) { m_i = i; }
       int getVar() { return m_i; }
    private:
       int i;
    };
    

    Was ist daran jetzt bösartig? oO
    lg



  • Als bösartig habe ich die Möglichkeit bezeichnet eine lokale statische Variable zu benutzen.

    Sowas wie

    int storageI(int i2, bool get){
        static int i = 0;
        if (get) return i;
        return i = i2;
    }
    
    int GetI(){
        return storageI(0, true);
    }
    
    void SetI(int i){
        storageI(i, false);
    }
    

    Dass man Variablen in eine Klasse packen kann war mir neu, läuft aber aufs selbe hinaus. Statt globaler Variablen jedes mal eine Klasse oder 3 Funktionen zu bauen erscheint mir reichlich unübersichtlich, dann doch lieber alle globalen Variablen in einem Header deklarieren.
    Und wenn man die Variable in ein Objekt packt muss man ja das Objekt irgendwie global machen, damit hat man ja nichts gewonnen.



  • nwp2 schrieb:

    Man kann versuchen alles so zu schreiben, dass man alle Variablen lokal hat und die immer nur per Parameter oder Returnwert übermittelt.

    Klingt gut!

    nwp2 schrieb:

    Blöderweise sind das alles C++-Klassen, also muss das Objekt bekannt sein.

    Ja, aber es muss nicht unbedingt global bekannt sein. Gibt es nur ein einziges Objekt der Klasse und braucht man das sehr oft (z.B. Logging)?

    nwp2 schrieb:

    Und wenn man die Variable in ein Objekt packt muss man ja das Objekt irgendwie global machen, damit hat man ja nichts gewonnen.

    Da hast du völlig Recht, eine globale Variable innerhalb der Klasse ist im Normalfall nicht besser. Teilweise erhält man allerdings einen Vorteil durch die Kapselung. Oder man will eine einzige Instanz garantieren (Singleton).

    In vielen Fällen kann man seine Klassenbeziehungen allerdings so strukturieren, dass es kaum noch globale Variablen gibt - wenn, dann sind diese nur innerhalb einer Übersetzungseinheit bekannt (diese Art der globalen Variablen halte ich für legitim). Dies erreicht man, indem man recht weit oben in der Hierarchie eine Klasse hat, die anderen Klassen Aufgaben erteilt. Um bestimmte Aufgaben zu erfüllen, erhalten diese anderen Klassen zusätzliche Parameter mit erforderlichen Informationen. So kann man einen Austausch zwischen mehreren Klassen realisieren und bleibt recht flexibel, weil die wichtigen Abläufe jeweils an einem Ort konzentriert sind und nicht wie bei globalen Variablen überall auftreten können. Dadurch reduzieren sich die Abhängigkeiten der einzelnen Programmteile, was wegen der gesenkten Komplexität die Kompilierzeit erhöhen kann und Fehler stark einschränkt.

    Kurz: Meiner Meinung nach werden globale Variablen (über mehrere Module hinweg) sehr oft dort verwendet, wo sie nicht nötig wären. Es gibt sehr wenige Dinge, die "einfach so da" sind und nirgends eingeordnet werden können.


Anmelden zum Antworten