Trennung Header und Hauptteil



  • Die Header- und Implementationsdateien müssen auch nicht den gleichen Namen haben, es ist jedoch empfohlen. Relevant ist nur, dass eine CPP-Datei die zugehörige H- oder HPP-Datei einbindet, um die Definitionen zu liefern.

    Andere Dateien können dann diesen Header wiederum einbinden, um die Schnittstelle zu nutzen.



  • Der Compiler schert sich nicht um Dateinamen.
    Ich versuch mal ne Erklärung: Der Compiler geht deine .cpp-Dateien eine nach der anderen von oben nach unten durch und versucht, deinen Code zu verstehen. Um das zu können, muss er alle Variablen/Funktionen/Klassen/Enums/... schon kennen, wenn du sie benutzt. Und diese machst du mit den sogenannten Deklarationen bekannt. Eine Funktions-Deklaration sieht entweder so aus: void foo( int a, float b ); oder gleich mit dem Körper, der Implementation/Definition hintendran.
    Dieses Datei-durchgehen macht der Compiler bei jeder .cpp-Datei. Wenn er damit fertig ist, hat er sich für jede .cpp-Datei (die übrigens vollkommen unabhängig voneinander durchgegangen werden) eine "Objekt-Datei" geschrieben. Der Compiler ist fertig, der Linker nimmt die ganzen "Objekt-Dateien" und fügt sie zu einem ausführbaren Programm oder einer Bibliothek oder was auch immer du angegeben hast, zusammen. Für den Linker ist nur wichtig, dass z.B. zu jeder Funktionsdeklaration auch eine -implementation vorhanden ist, also jede Funktion auch Code hat, der ausgeführt werden kann.

    Die Rolle, die dabei Header-Dateien spielen: Wenn du zwei .cpp-Dateien hast und in beiden die Funktion "foo" benutzen willst, müsstest du sie in beiden .cpp-Dateien erst deklarieren, damit der Compiler sie in beiden Dateien auch kennt. Header-Dateien gibt es nun, damit du die Deklaration nur ein mal schreiben musst, und zwar in die Headerdatei. Diese Headerdatei bindest du in jede .cpp-Datei ein, in der du die Funktion(en) brauchst, die in der Header-Datei deklariert sind. Einbinden heißt inkludieren und das wiederum bedeutet, dass der Compiler beim Treffen der #include-Zeile in der .cpp-Datei eben diese #include-Zeile durch den kompletten Inhalt der Headerdatei ersetzt. Diese geht er dann ja durch und kennt nun die Deklarationen.



  • Danke, für die Erklärung, allerdings habe ich mich vllt. etwas missverständlich ausgedrückt:Ich meinte dieses:

    //Header "Beispiel"
    void Beispiel();//Dekleration, Compiler kennt nun Signatur der Funktion (heißt doch so, oder?), aber noch nicht die Definition der Funktion.
    
    //Programm "Beispiel.cpp"
    #include "Beispiel"
    void Beispiel();Definition der Funktion
    {
        Sleep(500);Der Funktionskörper
    }
    
    //Programm "AnderesBeispiel.cpp"
    #include "Beispiel"
    int main()
    {
        Beispiel();//Kann ja gar nicht gehen
    }
    

    Ist jetzt erkennbar was ich nicht verstehe?



  • Du fragst dich also, wieso du die Funkion benutzen kannst, obwohl sie in einer anderen Datei implementiert ist? Das stünde jedenfalls hier:

    Badestrand schrieb:

    Dieses Datei-durchgehen macht der Compiler bei jeder .cpp-Datei. Wenn er damit fertig ist, hat er sich für jede .cpp-Datei (die übrigens vollkommen unabhängig voneinander durchgegangen werden) eine "Objekt-Datei" geschrieben. Der Compiler ist fertig, der Linker nimmt die ganzen "Objekt-Dateien" und fügt sie zu einem ausführbaren Programm oder einer Bibliothek oder was auch immer du angegeben hast, zusammen. Für den Linker ist nur wichtig, dass z.B. zu jeder Funktionsdeklaration auch eine -implementation vorhanden ist, also jede Funktion auch Code hat, der ausgeführt werden kann.

    Das bedeutet, dass der Funktionscode erst _nach_ dem kompilieren zusammengefügt wird.



  • Ja, das ist mir bekannt, nur die Datei "AnderesBeispiel.cpp" inkludiert ja nur die "Beispiel". In der Beispiel ist keine Funktionsdefinition angegeben, nur eine Dekleration. In der "Beispiel" ist nie die Rede von der "Beispiel.cpp" => Der Compiler interessiert sich nicht für die "Beispiel.cpp", der Linker doch auch nicht. In der "AnderesBeispiel.cpp" wird dann eine Funktion aufgerufen von der nur die Signatur bekannt ist, allerdings nicht der "Inhalt".
    Also: Wie soll das dann gehen? 🙄 😉 😃 🙂 *Passender Smilie nicht gefunden*



  • Fast2 schrieb:

    => Der Compiler interessiert sich nicht für die "Beispiel.cpp", der Linker doch auch nicht.

    Doch, und das ist eben genau der Punkt. Sofern du die CPP-Datei mitkompilierst, wird auch der Code der Definition kompiliert und befindet sich dann in einer Objektdatei. Mit der (anderen) Objektdatei der aufrufenden CPP-Datei zusammengelinkt ergibt sich dann die vollständige Funktion.



  • Wusste ich doch, dass wir mal wieder aneinander vorbeigeredet haben. 😃 Ich meinte:
    Beispiel.cpp und AnderesBeispiel.cpp inkludieren _nur_ Beispiel. Beispiel bindet gar nichts ein. Ich habe es auf diesen Fall bezogen: Ich habe irgendeine tolle Funktion, für ein Programm geschrieben und möchte diese nun in einem zweiten Programm benutzen. Also schreibe ich eine Headerdatei. Nur was kommt da rein? Laut dem dritten Beitrag nur die Dekleration. Daher die Frage: Woher nimmt der Compiler/Linker die Information welchen Code meine Funktion hat?



  • Fast2 schrieb:

    Wusste ich doch, dass wir mal wieder aneinander vorbeigeredet haben. 😃

    In der Tat. 😉

    Das habe ich ja oben erklärt, ich hab einfach nicht deine Dateinamen benutzt. Der Header "Beispiel" muss nichts inkludieren für die Funktionsdefinition. Es reicht, wenn das Modul, in dem die Definition steht, mitkompiliert wird. Der Linker wird den entsprechenden Verweis (durch die Deklaration bekannt gemacht) in den Objektdateien automatisch suchen. Wenn nicht genau eine Definition vorhanden ist, kommt es zu Linkerfehlern.



  • Also muss die AnderesBeispiel.cpp die Beispiel.cpp inkludieren?
    Und wie wäre es einfach gleich die ganze Definition in die Beispiel zu packen?



  • Fast2 schrieb:

    Also muss die AnderesBeispiel.cpp die Beispiel.cpp inkludieren?

    Nein. Lies dir bitte noch mal meine oberen beiden Posts durch. CPP-Dateien inkludiert man generell nicht.

    Fast2 schrieb:

    Und wie wäre es einfach gleich die ganze Definition in die Beispiel zu packen?

    Es wäre fehlerhaft, wenn der Header mehrere Male eingebunden wird, da die Funktion so mehrfach definiert ist.



  • @Fast2:
    Der Compiler macht aus jeder Übersetzungseinheit üblicherweise ein "Object-File".
    Übersetzungseinheit ist dabei normalerweise ein .cpp File.

    Der Compiler macht also aus a.cpp ein a.obj und aus b.cpp ein b.obj.
    Wenn in a.cpp eine Funktion verwendet wird die irgendwoanders implementiert ist, dann ist in a.obj ein Verweis auf eine "Funktion die irgendwoanders implementiert ist" (mit Namen natürlich).

    Dem Linker gibst du dann a.obj UND b.obj, und wenn in b.obj alle Funktionen implementiert sind die in a.obj fehlen, dann funktioniert das.

    Klar jetzt?


  • Administrator

    Vielleicht könnte man es mit ein paar g++ Befehle erklären. Dein präsentierter Code, würde man so kompilieren. Im ersten Schritt diesen Befehl:

    g++ -c Beispiel.cpp
    

    -c bedeutet nur kompilieren, keine ausführbare Datei zu erstellen. Dafür wird eine Beispiel.o erstellt. Eine sogenannte Objektdatei.

    Im zweiten Schritt setzen wir das nun zusammen:

    g++ -o Beispiel.exe AnderesBeispiel.cpp Beispiel.o
    

    -o bedeutet das man nun eine ausführbare Datei erstellt und benennt: Beispiel.exe. Man kompiliert AnderesBeispiel.cpp und fügt die Beispiel Objektdatei dazu. Der Linker wird dann die Definition in den Objektdateien (man kann auch mehrere benennen) suchen gehen, wenn er sie nicht in AnderesBeispiel.cpp findet.

    Grüssli



  • Dravere schrieb:

    ...
    -o bedeutet das man nun eine ausführbare Datei erstellt ...

    Nur eine Kleinigkeit der Vollständigkeit halber: -o bedeutet nur, dass die Ergebnisdatei einen bestimmten Namen bekommt (und nicht "a.out" bzw. "a.exe" heißen soll).
    Eine ausführbare Datei erstellt der Compiler allein dadurch, dass ihm kein Flag mitgibt, dass ihm das verbietet ( -c oder -P oder ...).

    Gruß,

    Simon2.



  • Ja schon 🙄 , den Vorgang des kompilierens/Linkens (wie heißt denn beides zusammen), maße ich mir an, verstanden zu haben. Nochmal die Fragestellung (jetzt andere Namen): In der "Programm1.cpp" habe ich irgendeine tolle Funktion. In "Programm2.cpp" möchte ich diese auch. Also was muss ich dann tun? Das ich eine Headerdatei brauche habe ich schon mittbekommen, nur was soll da rein? Eine Dekleration und ein Include der Datei "Programm1.cpp", oder? Nur kann es dadurch nicht auch zu mehrfachincludes kommen?



  • Fast2 schrieb:

    Das ich eine Headerdatei brauche habe ich schon mittbekommen, nur was soll da rein? Eine Dekleration und ein Include der Datei "Programm1.cpp", oder? Nur kann es dadurch nicht auch zu mehrfachincludes kommen?

    Und du bist sicher dass du das mit dem Kompilieren verstanden hast?
    Du willst eine Funktion in beiden Übersetzungseinheiten haben (dem Compiler ist egal, ob die Dateien nun xy.cpp oder xy.jkl heißen, der kennt nur Übersetzungseinheiten).
    Um das zu erreichen musst du den Code entweder per Hand in beide ÜEs schreiben oder ihn in eine Datei schreiben die du in beide ÜEs mit #include einbindest. Nachdem der Präprozessor rübergerutscht ist steht dann in jeder ÜE der Code den du eingebunden hast.
    Jetzt darfst du mal raten welche Dateien jeweils der Grundstock für die erste und zweite ÜE sind und welche Datei man wo einbinden sollte...



  • Fast2, du scheinst meine Posts nicht zu lesen. Ich habe dort vieles erklärt, und auch erwähnt, dass man nie CPP-Dateien einbinden sollte.



  • Fast2 schrieb:

    //Header "Beispiel"
    void Beispiel();//Dekleration, Compiler kennt nun Signatur der Funktion (heißt doch so, oder?), aber noch nicht die Definition der Funktion.
    
    //Programm "Beispiel.cpp"
    #include "Beispiel"
    void Beispiel();Definition der Funktion
    {
        Sleep(500);Der Funktionskörper
    }
    
    //Programm "AnderesBeispiel.cpp"
    #include "Beispiel"
    int main()
    {
        Beispiel();//Kann ja gar nicht gehen
    }
    

    evtl lag sein problem einfach nur an einer erneuten vorwärtsdeklaration :

    void Beispiel();Definition der Funktion <----??? kann nicht gehen
    {
        Sleep(500);Der Funktionskörper
    }
    
    eher so:
    
    void Beispiel() //Definition der Funktion 
    {
        Sleep(500);Der Funktionskörper
    }
    


  • LogInNameVergessen schrieb:

    evtl lag sein problem einfach nur an einer erneuten vorwärtsdeklaration :

    void Beispiel();Definition der Funktion <----??? kann nicht gehen
    {
        Sleep(500);Der Funktionskörper
    }
    
    eher so:
    
    void Beispiel() //Definition der Funktion 
    {
        Sleep(500);Der Funktionskörper
    }
    

    Ich denke, dass das eher ein Tippfehler ist. Er wird wohl schon gewusst haben, dass man nicht einfach Text im Programm stehen lassen kann. Was hat das überhaupt mit einer Vorwärtsdeklaration zu tun?



  • @LogInNameVergessen: Nein, das kommt daher, dass in AutoIt Semikola für Strichpunkte stehen, habe ich irgendwie durcheinandergebracht 😃 .
    @Nexus: Ich lese doch deine Posts... 😞 . Ich mache es auch gerne nochmal, und dann zitiere ich alles missverständliche per Edit.

    Edit:

    Nexus schrieb:

    Die Header- und Implementationsdateien müssen auch nicht den gleichen Namen haben, es ist jedoch empfohlen. Relevant ist nur, dass eine CPP-Datei die zugehörige H- oder HPP-Datei einbindet, um die Definitionen zu liefern.

    Andere Dateien können dann diesen Header wiederum einbinden, um die Schnittstelle zu nutzen.

    Also wie jetzt(?), um die Definitionen zu bekommen muss ich die Headerdatei einbinden?

    Badestrand schrieb:

    Die Rolle, die dabei Header-Dateien spielen: Wenn du zwei .cpp-Dateien hast und in beiden die Funktion "foo" benutzen willst, müsstest du sie in beiden .cpp-Dateien erst deklarieren, damit der Compiler sie in beiden Dateien auch kennt. Header-Dateien gibt es nun, damit du die Deklaration nur ein mal schreiben musst, und zwar in die Headerdatei. Diese Headerdatei bindest du in jede .cpp-Datei ein, in der du die Funktion(en) brauchst, die in der Header-Datei deklariert sind. Einbinden heißt inkludieren und das wiederum bedeutet, dass der Compiler beim Treffen der #include-Zeile in der .cpp-Datei eben diese #include-Zeile durch den kompletten Inhalt der Headerdatei ersetzt. Diese geht er dann ja durch und kennt nun die Deklarationen.

    Ja, ich soll die Deklerationen in die Header-Datei schmeißen, nur wohin mit den Definitionen?

    Dravere schrieb:

    Vielleicht könnte man es mit ein paar g++ Befehle erklären. Dein präsentierter Code, würde man so kompilieren. Im ersten Schritt diesen Befehl:

    g++ -c Beispiel.cpp
    

    -c bedeutet nur kompilieren, keine ausführbare Datei zu erstellen. Dafür wird eine Beispiel.o erstellt. Eine sogenannte Objektdatei.

    Im zweiten Schritt setzen wir das nun zusammen:

    g++ -o Beispiel.exe AnderesBeispiel.cpp Beispiel.o
    

    -o bedeutet das man nun eine ausführbare Datei erstellt und benennt: Beispiel.exe. Man kompiliert AnderesBeispiel.cpp und fügt die Beispiel Objektdatei dazu. Der Linker wird dann die Definition in den Objektdateien (man kann auch mehrere benennen) suchen gehen, wenn er sie nicht in AnderesBeispiel.cpp findet.

    Grüssli

    OK, nach mehrmaligem Lesen hat sich hier grad ein Missverständnis meinerseits aufgelöst, nur wie bringe ich den Compiler/Linker dazu das selbe ohne den Zwischenschritt zu machen?

    pumuckl schrieb:

    Und du bist sicher dass du das mit dem Kompilieren verstanden hast?
    Du willst eine Funktion in beiden Übersetzungseinheiten haben (dem Compiler ist egal, ob die Dateien nun xy.cpp oder xy.jkl heißen, der kennt nur Übersetzungseinheiten).
    Um das zu erreichen musst du den Code entweder per Hand in beide ÜEs schreiben oder ihn in eine Datei schreiben die du in beide ÜEs mit #include einbindest. Nachdem der Präprozessor rübergerutscht ist steht dann in jeder ÜE der Code den du eingebunden hast.
    Jetzt darfst du mal raten welche Dateien jeweils der Grundstock für die erste und zweite ÜE sind und welche Datei man wo einbinden sollte...

    Naja, jetzt bin ich mir doch nicht mehr so sicher. 😃
    Gut, du schreibst, dass ich raten soll welche Datei das sein könnte: Ich hätte jetzt gesagt: Die Headerdatei und diese in die CPP-Dateien einbinden. Was sich allerdings dem ersten Post von Nexus wiederspricht(erster Listenpunkt):

    Nexus schrieb:

    Header: Schnittstelle für den Benutzer, soll inkludiert werden

    • Deklarationen von Funktionen und Variablen
    • Klassendefinitionen
    • Inline-Funktionen
    • Templates
    • Namensräume, Typedefs, Makros
    • evtl. Kommentare, die Funktionen beschreiben

    Ich hoffe ihr versteht mich so besser, und habt mich noch nicht aufgegeben. 😉



  • Hmm. Habe jetzt nicht alles mitverfolgt, aber mal ganz einfach:

    irgendwas.h

    #ifnedf IRGENDWAS_H //Headerguards
    #define IRGENDWAS_H
    
    class foo
    {
    public:
        void bar (); // Deklaration
    };
    
    #endif
    

    irgendwas.cpp

    #include "irgendwas.h" // Klassendefinition includen
    #include <iostream> //damit wir cout  benutzen können :yum: 
    
    void foo::bar () //Definition der in der Klasse deklarierten Funktion
    {
       std::cout << "irgendwas";
    }
    

    main.cpp

    #include "irgendwas.h" //Klassendefinition include, da wir diese Klasse benutzen wollen
    
    int main ()
    {
        foo f;
        f.bar ();
    }
    

    Was dich ev. verwirrt ist das ganze Deklaration/Definitions Zeugs. 😉
    Was das bei einer Funktion bedeutet hast du gesehen. Nun kann man das ganze auch für eine Klasse machen..

    irgendwas_anderes.h

    //.. Headerguards..
    
    class foo; // Deklaration von foo,weil die Implementierung nicht wichtig ist (hier), da es nur ein Zeiger ist..
    
    class ptr
    {
        foo* p;
    public:
        void machwas (); // wieder das gleiche, wie oben
    };
    

    irgendwas_anderes.cpp

    #include "irgendwas_anderes.h" // Wir müssen wissen, wie die Schnittstelle aussieht, damit wir die Funktionen implemenieren können.
    #include "irgendwas.h" // Müssen wir nun auch wissen, damit wir den ptr verwenden können.
    
    void ptr::machwas () //dasselbe
    {
        p->bar ();
    }
    

    Wenn du jetzt noch verwirrter bist, dann schlaf drüber und schau dir das nochmal in Ruhe an. 😉 Wenn du glaubst es verstandne zu haben, googlest du mal nach pImpl - Idiom und versuchst das zu verstehen. Wenn du das dann wirklich verstehst, dann hast du das ganze Spiel verstanden. 😉

    Noch eins:
    Der Name der Header und der cpp müssen nicht gleich sein, das ist nur so Konvention, weil alles andere keinen Sinn machen und verwirren würde. (eigl. logisch, dass die gleich heissen, nicht?)
    Wichtig ist aber nur, dass die Header includes stimmen (worauf dich aber dein Compiler sonst freundlich aufmerksam macht. ;))


Anmelden zum Antworten