Anfängerfrage: Header > Preproceser > Quelldatei: Funktionsweise & Unnötiger Code?



  • Erstmal Hallo

    Mir ist klar, den meisten wäre es egal, solange es funktioniert, mich würde es jedoch trotzdem interessieren.

    Also:
    Quellcodedateien in C++ beginnen mit (zB.) "#include <>"
    was dann auf eine "Headerdatei" verweist.

    Die Quellcodedatei ist eine Textdatei die man compilieren muß, damit daraus eine Ausführbare Datei wird.

    So weit so gut - das ist Basic-Wissen.
    Jetzt hab ich jedoch gelesen, genau genommen ist der Compiler ja eigentlich nur das Mittelstück des "Compiler", zwischen Präprozessor und Linker.

    Mir ist dabei jetzt zwar klar wofür Compiler, Präprozessor und Linker gut sind, bin mir jedoch noch unklar darüber was der Präprozessor genau macht.

    Ich sehe dabei jetzt 2 Möglichkeiten, wobei die 1.Möglichkeit daß wäre, was ich gerne hätte und die 2.Möglichkeit, die Frage aufwirft, ob sich #include <>" auch irgend wie, ich sage mal eingrenzen läßt.

    Nehmen wir als Beispiel dafür, was ich meine, die "Hello World"-Anwendung:

    #include <iostream> 
    
    int main()
    {              
        std::cout << "Hallo, du schöne Welt!" << std::endl; 
        return 0; 
    }
    

    So wie ich die Funktionsweise des Präprozessor verstanden habe ersetzt er "include <iostream>" mit dem Inhalt der Headerdatei " iostream"

    Von iostream wird hier jedoch nur "cout" & "endl" benötigt.

    Das Effizenteste wäre es also, was ich für unwahrscheinlich halte, wenn der Präprozeser erkennt, welche Teile der Headerdatei genutzt werden, und somit auch nur diese Teile in die Text Datei kopiert.

    Wenn jedoch, wie ich glaube, der Präprozeser stupiedes Copy/Past betreibt, gibt es dann irgend eine Möglichkeit ... zB.:
    "include <iostream/cout>"
    "include <iostream/endl>"

    Zwei Dinge sind es dabei, die mich an Möglichkeit 2 stören.
    Zum einen kann es sein, daß eine Headerdatei ja ebenfalls auf eine Headerdatei verweisen kann, usw.
    Benutze ich also eigene "Headerdatein" könnte es übertrieben gesagt sogar vorkommen, daß der Präprozeser aus einer Text-Datei mit gerademal 7 Zeilen eine Textdatei mit 7GB erzeugt.

    Zum anderen besteht ein Programm aus mehreren Textdatein, die alle mehr oder weniger auf die selben Headerdateien verweisen.
    Ich habe dann also am Ende mehrere Dateien die alle großteils den selben Code enthalten, der in der jeweiligen Textdatei zudem womöglich "größten" Teils unnötig ist. Der dann trotzdem vom Compiler in Maschinencode übersetzt wird, und den dann besten falls der Linker wieder entfernt (soweit zumindest mein Befürchtung).

    Aber womöglich verstehe ich auch einfach nur die Funktionsweise falsch, da diese nur vereinfacht erklärt wird, da es bei Anfängern ja nur um ein grundsätzliches Verständnis geht, jene die "Compiler" schreiben ohnehin genau wissen wie ein "Compiler" eine Textdatei in ausführbaren Code umwandelt, und es bei allen anderen im Alltag eine untergeordnete Rolle spielt.



  • @axam In iostream steht nicht die Definition (der Code) zu cout sondern die Deklaration.

    Somit weiß der Compiler, wie cout zu benutzen ist.

    Das ist nicht so viel Code.

    cout selber ist dann in der C++ Runtime Library.



  • Das hast du leider zum Großteil richtig erfasst. #include ist tatsächlich simple Textersetzung. Theoretisch muss das bei Standardheadern wie <iostream> nicht so sein, ist es aber in der Praxis.



  • Deine Überlegung ist richtig. Das ganze Verfahren ist sehr ineffizient. Das Problem wird mit den in C++ 20 eingeführten Modules hoffentlich gelöst.

    Die meisten Compiler bieten precompiled header an, die den Arbeitsaufwand etwas reduzieren.



  • Zusatzfrage:
    Interpretiere ich die Aussage von @DirkB dahingehend richtig, daß im ausführbaren Programm eine "C++ Runtime Library" enthalten ist, die jedoch nur einmal vorhanden ist, und deren Funktionen bei Bedarf ausgeführt werden.
    ... oder werden deren Funktionen im Laufe des Compelierungsvorgang an den entsprechenden Stellen in den "Quellcode" eingefügt.
    ... oder ?

    Da nun für mich feststeht, daß während dem präprozes "unnötiger" Code eingefügt wird, stellt sich nun natürlich auch die Frage ob und wann dieser später auch wieder entfernt wird.
    Gibt es Möglichkeiten Code dahingehend zu optimieren das unnötiger "Code" im ausführbaren Programm weitgehend vermieden wird, oder um ihn irgend wann vor dem Linken irgendwie wieder zu entfernen.
    Sprich, ist das ganze nur eine Zeitfrage während dem Compelieren oder hat es auch Auswirkungen auf das ausführbare Programm?



  • @axam sagte in Anfängerfrage: Header > Preproceser > Quelldatei: Funktionsweise & Unnötiger Code?:

    daß im ausführbaren Programm eine "C++ Runtime Library" enthalten ist, die jedoch nur einmal vorhanden ist, und deren Funktionen bei Bedarf ausgeführt werden.

    Die Runtimelibrary kann statisch oder dynamisch gelinkt werden.

    Statisch ist sie dann in der .exe drin, natürlich nur einmal.
    Dynamisch als DLL, dann ist sie eine extra Datei, die zur Laufzeit geladen wird.

    ... oder werden deren Funktionen im Laufe des Compelierungsvorgang an den entsprechenden Stellen in den "Quellcode" eingefügt.

    Ja, bei Inline-Funktionen

    Da nun für mich feststeht, daß während dem präprozes "unnötiger" Code eingefügt wird,

    Das siehst du falsch.
    Du solltest da zwischen Code (der irgendwann Speicher belegt) und Informationen (für den Compiler) unterscheiden.



  • @DirkB
    Vielen Dank für deine Antwort.
    Bei deiner letzte Antwort möchte ich jedoch nochmal nachhacken, den sie trifft genau den Kern.
    Mir ist durchaus bewußt, daß es einen Unterschied gibt zwischen Quellcode, Compilercode und ausführbarem Programm.
    @DirkB sagte in Anfängerfrage: Header > Preproceser > Quelldatei: Funktionsweise & Unnötiger Code?:

    @axam sagte in Anfängerfrage: Header > Preproceser > Quelldatei: Funktionsweise & Unnötiger Code?:

    Da nun für mich feststeht, daß während dem präprozes "unnötiger" Code eingefügt wird,

    Das siehst du falsch.
    Du solltest da zwischen Code (der irgendwann Speicher belegt) und Informationen (für den Compiler) unterscheiden.

    Und genau diesen Unterschied versuche ich gerade herauszufinden.

    Wie ich herausgefunden habe (und meine Befürchtung wurde dann hier im Forum bestätigt) fügt der Präprozesor "unnötigen" Code in meinen QuellCode ein.

    Wenn dieser "unnötige" Code dann vom Compiler in Maschinensprache übersetzt wird und vom Linker dann in das ausführbare Programm übernommen wird, würde ich gerne irgend wie das ausführbare Programm "bereinigen" - zwar ungeachtet der Sinnhaftigkeit des Ganzen, jedoch am Besten ohne einen eigenen "Compiler" schreiben zu müssen.

    Wenn andererseits der Präprozeser, zwar "unnötig" Code eingefügt, dafür aber dann der Compiler oder der Linker alles ignoriert was unnötig ist, dann wäre das ganze, vor allem jetzt wo ich weiß, daß auch die eigentlichen Funktionen "seperat" (also nur einmal) eingefügt werden, nur ein sekundäres Problem, das ich, solange sich die Compeilerzeit noch in Grenzen hält, vorerst vernachlässigen kann.





  • In den Headerdateien stehen nur Deklarationen (z.B. Makros, Templates, Klassen- und Funktionsdeklarationen, ...), welche nur Informationen für den Compiler (bzw. Präprozessor) sind, um Namen ("Identifiers") (und dahinterstehende Typen) zu erkennen.
    Nur wenn im Source-Code selbst eine dieser Namen benutzt wird, dann wird auch effektiv Code dafür erzeugt.

    Solange du also nur z.B. std::cout und std::endl aus <iostream> benutzt, wird auch nur dafür (bei statischer Einbindung der C++ Runtime Library) Code in die ausführbare Datei übernommen.
    Und bei Inline-Funktionen wird dann auch für jeden Aufruf Code erzeugt.

    Nur bei nicht-inline Funktionen oder dynamischer Einbindung von Library Funktionen wird nur ein Verweis auf das Objekt (Variable oder Funktion) in der erzeugten "object"-Datei (".obj") hinterlegt, so daß der Linker dann diese Abhängigkeiten auflösen kann (anhand der ".lib"-Dateien der Libraries).
    Und erst beim Ausführen des Programms werden dann die zugehörigen DLLs (in denen der Code für die externen Funktionen enthalten ist) vom Betriebssystem gesucht und geladen (und intern dann die passenden Funktionszeiger [per "Relokationseintrag"] dafür gesetzt), s.a. Portable Executable (PE) sowie der verlinkte Artikel Common Object File Format (wenn du verstanden hast, wie EXE- und DLL-Dateien etc. aufgebaut sind, dann kriegst du ein besseres Verständnis dafür, was der Compiler und Linker intern genau machen).



  • @axam sagte in Anfängerfrage: Header > Preproceser > Quelldatei: Funktionsweise & Unnötiger Code?:

    Wie ich herausgefunden habe (und meine Befürchtung wurde dann hier im Forum bestätigt) fügt der Präprozesor "unnötigen" Code in meinen QuellCode ein.

    Ich versuche dich gerade von dieser Angst abzubringen.

    Wenn dieser "unnötige" Code dann vom Compiler in Maschinensprache übersetzt wird

    Solcher Code steht nicht in Headerdateien - also Etwas, das in Maschinensprache übersetzt wird.

    PS: der Compiler hat mehr Verwaltungsaufwand, da er Informationen erhält die er später nicht braucht. Das macht die .exe aber nicht größer.



  • Genau, dies würde dann die One Definition Rule (ODR) verletzten und vom Linker angemeckert werden.



  • Dann sage ich jetzt danke an alle. Von meiner Seite her ist meine Frage jetzt soweit beantwortet.

    Und dank an @Th69, dafür, daß du die Funktionsweise etwas ausführlicher aufgeschlüsselt hast, jetzt verstehe ich das Zusammenspiel der einzelnen Komponenten (und was am ende als 0&1 übrigbleibt) schon deutlich besser - und für meine Zwecke erstmal ausreichend genug.