Anfängerfrage: globale Variablen in VS? (Insb. Vermeindung von LNK2005 bei mehrfacher Verwendung eines Namens)



  • LowFly schrieb:

    wenn ich das bisher richtig verstanden hab dann macht der compiler keinen unterschied zwischen A.h, A.cpp & B.h, B.cpp das sind für den nur dateien die er nacheinader einliest & aus 5 verschiedenen dateien 1 große macht.

    steht jetzt in A.cpp auserhalb einer classe ne deklaration zb.

    CString m_sGlobal;
    

    kann diese nur in der A.cpp angesprochen werden steht aber im gloabalne bereich der durch nix geschützt ist (classe).
    deklarierst du nun in B.cpp die selbe variable nochmal, steht sie für dich scheinbar in einer anderen datei aber für den compiler nicht.

    setzt du nun das extern vor die variable

    extern CString m_sGlobal;
    

    Ah ja, jetzt verstehe ich sowohl besser, was der Compiler macht, als auch besser, was der extern Vorschlag in der stdafx.h sollte.

    Nur eins verstehe ich dann noch nicht:

    in der jeweiligen *.cpp (ich machs wenn dann in der stdafx.cpp)

    CString m_sStringGlobal;
    

    wieder auflösen und somit kannst du die in jeder classe in deinem project verwenden.

    Was heißt hier "auflösen"?

    Danke und Gruß,
    Dong



  • auflösen, bekanntmachen wie auch immer.
    variable nicht aufgelöst (o. so ähnlich) bringt der compiler als fehlermeldung wenn du irgendwo in deinem project

    extern CString m_GlobalString;
    

    schreibst und die variable m_GlobalString zb in deinem Dialog wie unten folgt verwendest

    CDialog::OnInitDialog()
    {
        m_GlobalString = "Ich bin eine globale Variable";
        MessageBox(m_GlobalString);
    ....
    }
    

    OHNE sie vorher in diesem fall in der Dialog.cpp oberhalb der classe wie folgt aufzulösen.

    CSting m_GlobalString;
    

    ich mach das halt immer in der stdafx.h / .cpp da dies eine der ersten dateien ist die der compiler einliest bzw. diese in jeder datei als erste als include stehn. somit brauch ich mir dann keine gedanken mehr machen hab ich sie nun aufgelöst oder nicht & wenn ja wo. die stdafx sehen alle classen somit gloabal.

    mfg
    LowFly



  • Danke, jetzt habe ich es verstanden!


  • Administrator

    LowFly hat das ganz gut erklärt, trotzdem habe ich noch was zu deinem PS.

    dong schrieb:

    P.S.:
    Warum globale Variablen und nicht Membervariablen?
    Für mindestens zwei Dinge:

    1. Ich verwende Threadfunktionen, von denen man nicht direkt auf alle Membervariablen zugreifen kann, da Threadfunktionen statische Funktionen sein müssen. Daher behelfe ich mir mit dem Trick, dass ich globale Zeiger benutze um auf Membervariablen vom Thread aus zuzugreifen.

    Du kannst einem Thread aber auch einen Zeiger übergeben. Daher muss das Zeugs nicht immer über Globale Variablen gehen. Ich würde sowieso vorschlagen, dies gerade nicht zu tun. Wie kannst du nun mehrere Dinge über nur einen Zeiger übergeben? Kein Problem:

    struct SGiveThread
    {
        int nInt;
        float fFloat;
        CString strName;
        CEinObjekt* pEinObjekt;
        // usw. oder was auch immer
    };
    
    UINT CDeineKlasse::ThreadFunction(LPVOID pParam)
    {
        SGiveThread* pGiveThread = static_cast<SGiveThread*>(pParam);
    
        // pGiveThread auslesen.
    
        // Speicher freigeben.
        delete pGiveThread;
    }
    
    void CDeineKlasse::StartThread()
    {
        SGiveThread* pGiveThread = new SGiveThread;
    
        // pGiveThread füllen.
    
        AfxBeginThread(CDeineKlasse::ThreadFunction, pGiveThread);
    }
    

    dong schrieb:

    1. Für Nummerierungen mit enum: Das sind zwar nicht direkt globale Variablen, aber für sie gelten die ähnliche Probleme:
      z.B.
    //Beispiel1.h
    enum Randbedingungen
    {
    	fest, offen, periodisch
    };
    
    //Beispiel2.h
    enum Randbedingungen2
    {
    	fest, offen, periodisch
    };
    

    führt zu einem ähnlichen Error:

    error C2365: 'fest' : redefinition; previous definition was 'enumerator': see declaration of 'fest'

    Aber zugegeben, die sind wohl doch ein anderes paar Steifel und die könnte ich vielleicht wirklich in LowFlys Sinne global anlegen, mal gucken, ob das klappt... 🙂

    Jo also, wenn du ein Enum hast mit zweimal dem gleichen Inhalt, da ist es schon sinnvoll, dass du da das gleiche brauchst. Und sonst definier halt einen anderen Namen, wenn es einen anderen Inhalt hat. Oder nimm die Inhalt zusammen und deklarier es nur einmal. Also das Problem wirst du wohl mit einer sinnvollen Strukturierung wegbekommen.

    Ajo ... nochwas. Wenn du mehrere Sachen Global anlegst, ist es vielleicht eine Überlegung wert, dass du sie in ein Namespace nimmst.

    // stdafx.h
    namespace Global
    {
        // Irgendwelche Variablen.
    }
    

    Aber sowas sollte glaub ich schon in der FAQ stehen ^^

    Grüssli



  • *mich doch nochmal meld*
    Danke für Deine Ergänzungen, Dravere, insbesondere die Verpackung meherer Werte in einem Zeiger könnte mir noch nützlich sein. 🙂
    Allerdings:

    Dravere schrieb:

    dong schrieb:

    Ich verwende Threadfunktionen, von denen man nicht direkt auf alle Membervariablen zugreifen kann, da Threadfunktionen statische Funktionen sein müssen. Daher behelfe ich mir mit dem Trick, dass ich globale Zeiger benutze um auf Membervariablen vom Thread aus zuzugreifen.

    Du kannst einem Thread aber auch einen Zeiger übergeben. Daher muss das Zeugs nicht immer über Globale Variablen gehen. Ich würde sowieso vorschlagen, dies gerade nicht zu tun.

    Also:
    Wenn ich dem Thread jede Variable, auf die er zugreifen muss, einzeln in einem struct verpackt übergebe, wäre das schon eine ganz schön lange Liste, zumal sich diese Variablen während Ablauf des Threads ändern können, d.h. ich müßte für jede Varaible erst mal einen Zeiger erstellen, damit der Thread Zugriff auf den aktuellen Wert hat.Dann würde ich am besten ja gleich nur Zeiger auf die verwendeten Objekte übergeben (statt auf deren Member). Und dann ist es ja eigentlich nur ein kleiner Unterschied zu meinen globalen (im Sinne von: Geltungsbereich = dateiweit) Zeigern auf eben diese Objekte.
    Also der einzige Nachteil, den ich bei meinen gloablen Zeigern auf die verwendeten Objekte erkennen kann ist, dass ich die Threadfunktion nicht so ganz ohne weiteres in eine andere Datei copy&pasten könnte. Aber ansonsten...?

    Und zu der Sache mit den Nummerierungen enum:
    Das war tatsächlich ganz einfach:
    In die stdafx.h damit und fertig, die Nummerierung steht dann überall zur Verfügung (und so ist das auch ganz praktisch für mich...:) )

    Gruß,
    dong


  • Administrator

    Der Vorteil von einem Struct ist eine Kapselung. Die Variablen liegen nicht einfach so frei im nichts rum und sind von jedem aufgreifbar. Du kannst mit einem Struct natürlich Referenzen, Zeiger aber auch Werte/Kopien dem Thread übergeben. Du kannst ja dann auch gleich mit dem Struct-Zeiger im Thread arbeiten und musst nicht zuerst alles rauskopieren oder so.

    Diese Kapselung erhöht meiner Meinung nach die Übersicht. Der Code wird lesbarer, man versteht woher welche Informationen kommen.

    Noch so als Zusatz: Zur Kommunikation zwischen den Threads sollte man auf CEvent's und PostThreadMessage / PostMessage setzen. Gleichzeitiges schreiben und lesen auf eine Variable, kann böse folgen haben.

    Grüssli



  • Dravere schrieb:

    Der Vorteil von einem Struct ist eine Kapselung. Die Variablen liegen nicht einfach so frei im nichts rum und sind von jedem aufgreifbar. Du kannst mit einem Struct natürlich Referenzen, Zeiger aber auch Werte/Kopien dem Thread übergeben. Du kannst ja dann auch gleich mit dem Struct-Zeiger im Thread arbeiten und musst nicht zuerst alles rauskopieren oder so.

    Ah ja. Da ich alleine programmiere, bin ich eigentlich immer ganz froh, wenn ich auf alles möglich zugreifen kann, aber ich sehe ein, dass das wohl eher schlechter Stil ist. 🙂

    Dravere schrieb:

    Diese Kapselung erhöht meiner Meinung nach die Übersicht. Der Code wird lesbarer, man versteht woher welche Informationen kommen.

    Das klingt in der Tat vernünftig....

    Dravere schrieb:

    Noch so als Zusatz: Zur Kommunikation zwischen den Threads sollte man auf CEvent's und PostThreadMessage / PostMessage setzen. Gleichzeitiges schreiben und lesen auf eine Variable, kann böse folgen haben.

    Selbstgemachte CEvens und so kann ich noch nicht. Ist aber nicht so schlimm, bei kritischen Variablen habe ich dafür extra zwei Versionen der selben Variablen angelegt: Die eigentliche Variable und einen Puffer, der Wert des Puffers wird immer nur zu bestimmten, geeigneten Zeitpunkten der Variable übergeben. So sollte es hoffentlich auch erstmal gehen.

    Aber wenn ich Zeit habe, werde ich mir die CEvents Geschichte mal näher ansehen, danke für den Hinweis. 🙂



  • Ich habe mittlerweile doch noch eine Möglichkeit gefunden das umzusetzen, was ich ürsprünglich vorhatte:
    Zwei verschiedene Variablen mit gleichen Namen und Dateigeltungsbereich.
    (Zur Erinnerung:)

    dong schrieb:

    ...
    Nicht erwünscht ist hingegen, dass ich in Beipiel1.cpp und in Beispiel2.cpp nicht den gleichen Namen für diese Art globale Variable verwenden darf, dann kommt eben der blöde LNK2005 Error:

    //Besipiel1.cpp:
    #include "Beispiel1.h"
    
    //"meine" globale Variable:
    int test;
    
    //Besipiel2.cpp:
    #include "Beispiel2.h"
    
    //"meine" globale Variable:
    int test;
    

    geht deshalb eben nicht.

    Und das ist (eine meiner zwei) Frage(n): Wie kann ich dafür gleiche Namen verwenden (die Variablen haben ja schließlich verschiedene Gültigkeitsbereiche!), aber LNK2005 vermeiden?

    Und zwar geht das folgendermaßen:
    Man legt diese Variablen einfach in namespaces:

    //Besipiel1.cpp:
    #include "Beispiel1.h"
    
    namespace ns_beispiel1
    {
        int test;
    }
    using namespace ns_beispiel1;
    ...
    
    //Besipiel2.cpp:
    #include "Beispiel2.h"
    
    namespace ns_beispiel2
    {
        int test;
    }
    using namespace ns_beispiel2;
    ...
    

    --> test in Beispiel1 und test in Beispiel2 sind zwei verschiedene, voneinander unabhängige Variablen mit Dateigeltungsbereich, und das ohne LNK2005 Error. 🙂

    Wohlgemerkt geht das nur dann, wenn man diese Variable innerhalb einer namespae Deklaration in der cpp Datei macht, im Header treten komischerweise trotzdem die LNK2005 Error auf.

    Gruß,
    Dong


  • Administrator

    Tolle Lösung, und wieso ein extra Namespace machen und nicht einfach

    // A.cpp
    int test1;
    
    // B.cpp
    int test2;
    

    LOL 😃

    Grüssli



  • Dravere schrieb:

    Tolle Lösung, und wieso ein extra Namespace machen und nicht einfach

    // A.cpp
    int test1;
    
    // B.cpp
    int test2;
    

    Weil ich in meinen Pendants zu Beispiel1 und Beispiel2 um Teil gleiche Codefragmente nutze, die sich so endlich direkt copy und pasten lassen ohne das ich etwas ersetzen müßte. Das Ersetzen (auch mit STRG + h...) scheint mir nämlich mehr Arbeit als die Erstellung der namespaces...

    ...außerdem habe ich jetzt, da ich die namespaces kennengelernt habe, den Eindruck, dass die Variablen darin sauberer aufgehoben sind, als wenn sie irgendwo frei darum liegen. (Und z.B. möglicherweise viel zu früh angelegt werden, auch, wenn sie noch gar nicht benötigt werden, aber sie werden eben als global (im Sinne Projektweit) angesehen sonst...)

    Gruß,
    Dong


Anmelden zum Antworten