C-Sprache wird kompiliert, um eine „.OBJ“-Binärdatei (Objektdatei) zu generieren. In der C-Sprache wird nach der Kompilierung des Quellprogramms (.c-Datei) durch den Compiler eine Binärdatei (Objektdatei genannt) mit dem Suffix „.OBJ“ generiert, die schließlich als „Link“ bezeichnet wird verbindet diese „.OBJ“-Datei mit verschiedenen Bibliotheksfunktionen der C-Sprache, um eine ausführbare Datei mit der Endung „.EXE“ zu generieren.
Die Betriebsumgebung dieses Tutorials: Windows 7-System, c99-Version, Dell G3-Computer.
Kompilierung von C-Sprachquelldateien
Der Suffixname der C-Sprachquelldatei ist „.c“, der Suffixname der kompilierten Datei ist „.obj“ und der Suffixname der verbundenen ausführbaren Datei ist „.exe“.
Schritte zum Erstellen eines Programms in C-Sprache:
Bearbeiten: Es geht darum, den Quellcode des C-Programms zu erstellen und zu ändern – das Programm, das wir schreiben, wird Quellcode genannt.
Kompilierung: Es dient dazu, den Quellcode in Maschinensprache umzuwandeln. Die Ausgabe des Compilers wird zum Objektcode, und die Datei, in der sie gespeichert werden, wird als Objektdatei bezeichnet. Die Erweiterung ist .o oder .obj. (Dieser Teil der Kompilierung bezieht sich auf den Assembler, der die Assemblersprache kompiliert, oder den Compiler, der die Hochsprache kompiliert.)
Verknüpfung: Der Linker kombiniert den Quellcode in verschiedenen vom Compiler generierten Modulen und fügt ihn dann aus der von bereitgestellten Bibliothek hinzu die C-Sprache Notwendige Codemodule und kombinieren sie in einer ausführbaren Datei. Die Erweiterung lautet .exe unter Windows und hat unter Unix keine Erweiterung.
Ausführen: Führen Sie das Programm aus.
Nachdem das C-Sprachquellprogramm vom C-Sprachcompiler kompiliert wurde, wird eine Binärdatei (Objektdatei genannt) mit dem Suffix „.OBJ“ generiert. Anschließend wird eine Software namens „Link“ verwendet „.OBJ“-Datei mit verschiedenen Bibliotheksfunktionen, die von der C-Sprache bereitgestellt werden, um eine ausführbare Datei mit dem Suffix „.EXE“ zu generieren. Offensichtlich kann die C-Sprache nicht sofort ausgeführt werden.
Das Prozessdiagramm sieht wie folgt aus:
Wie Sie auf dem Bild sehen können, ist der Kompilierungsprozess des gesamten Codes in zwei Prozesse unterteilt: Kompilierung und Verknüpfung entsprechen dem in geschweiften Klammern eingeschlossenen Teil Abbildung, und der Rest ist der Verknüpfungsprozess.
Kompilierungsprozess
- Der Kompilierungsprozess kann in zwei Phasen unterteilt werden: Kompilierung und Montage.
Kompilierung: Die Kompilierung besteht darin, das Quellprogramm (Zeichenstrom) zu lesen, es lexikalisch und grammatikalisch zu analysieren und hochsprachliche Anweisungen in funktional äquivalenten Assemblercode umzuwandeln. Der Kompilierungsprozess der Quelldatei umfasst zwei Hauptschritte Phasen:
Die erste Phase ist die Vorverarbeitungsphase, die vor der formellen Kompilierungsphase stattfindet. In der Vorverarbeitungsphase wird der Inhalt der Quelldatei gemäß den in der Datei platzierten Vorverarbeitungsanweisungen geändert. Beispielsweise ist die #include-Direktive eine Vorverarbeitungsdirektive, die den Inhalt der Header-Datei zur .cpp-Datei hinzufügt. Diese Methode zum Ändern von Quelldateien vor der Kompilierung bietet große Flexibilität bei der Anpassung an die Einschränkungen verschiedener Computer- und Betriebssystemumgebungen. Der für eine Umgebung erforderliche Code kann sich von dem für eine andere Umgebung erforderlichen Code unterscheiden, da die verfügbare Hardware oder die Betriebssysteme unterschiedlich sind. In vielen Fällen können Sie Code für verschiedene Umgebungen in derselben Datei ablegen und den Code dann während der Vorverarbeitungsphase ändern, um ihn an die aktuelle Umgebung anzupassen.
Hauptsächlich beschäftige ich mich mit folgenden Aspekten:
-
Anweisungen zur Makrodefinition, z. B. #define a b
- Für diese Art von Pseudoanweisung muss bei der Vorkompilierung lediglich alles a durch b im Programm ersetzt werden, a als String-Konstante wird jedoch nicht ersetzt. Es gibt auch #undef, das die Definition eines bestimmten Makros aufhebt, sodass zukünftige Vorkommen der Zeichenfolge nicht mehr ersetzt werden.
-
Bedingte Kompilierungsanweisungen wie #ifdef, #ifndef, #else, #elif, #endif usw.
- Die Einführung dieser Pseudoanweisungen ermöglicht es Programmierern, durch die Definition verschiedener Makros zu entscheiden, welche Codes vom Compiler verarbeitet werden sollen. Der Precompiler filtert unnötigen Code basierend auf relevanten Dateien heraus.
-
Die Header-Datei enthält Anweisungen wie #include 'FileName' oder #include usw..
- In Header-Dateien wird die Direktive #define im Allgemeinen verwendet, um eine große Anzahl von Makros zu definieren (die häufigsten sind Zeichenkonstanten) und enthält auch Deklarationen verschiedener externer Symbole. Der Hauptzweck der Verwendung von Header-Dateien besteht darin, bestimmte Definitionen mehreren verschiedenen C-Quellprogrammen zur Verfügung zu stellen. Denn im C-Quellprogramm, das diese Definitionen verwenden muss, müssen Sie nur eine #include-Anweisung hinzufügen, ohne diese Definitionen in dieser Datei wiederholen zu müssen. Der Precompiler fügt alle Definitionen in der Header-Datei zur Ausgabedatei hinzu, die er zur Verarbeitung durch den Compiler generiert. Die im C-Quellprogramm enthaltenen Header-Dateien können vom System bereitgestellt werden. Diese Header-Dateien werden im Allgemeinen im Verzeichnis /usr/include abgelegt. Verwenden Sie spitze Klammern (), um sie in das Programm einzubinden. Darüber hinaus können Entwickler auch eigene Header-Dateien definieren, die im Allgemeinen im selben Verzeichnis wie das C-Quellprogramm abgelegt werden. In diesem Fall sollten in #include doppelte Anführungszeichen ('') verwendet werden.
-
Spezielle Symbole, der Precompiler kann einige spezielle Symbole erkennen
- Zum Beispiel wird das im Quellprogramm erscheinende LINE-Logo als aktuelle Zeilennummer (Dezimalzahl) und FILE als Name des aktuell kompilierten C-Quellprogramms interpretiert. Der Precompiler ersetzt Vorkommen dieser Zeichenfolgen im Quellprogramm durch entsprechende Werte.
Was der Precompiler tut, ist im Grunde das „Ersetzen“ des Quellprogramms. Nach dieser Ersetzung wird eine Ausgabedatei ohne Makrodefinitionen, ohne Anweisungen zur bedingten Kompilierung und ohne spezielle Symbole generiert. Die Bedeutung dieser Datei ist dieselbe wie die der nicht vorverarbeiteten Quelldatei, der Inhalt ist jedoch unterschiedlich. Anschließend wird diese Ausgabedatei als Ausgabe des Compilers in Maschinenanweisungen übersetzt.
Die zweite Stufe der Kompilierung und Optimierung In der vorkompilierten Ausgabedatei gibt es nur Konstanten wie Zahlen, Zeichenfolgen, Variablendefinitionen und C-Sprachschlüsselwörter wie main, if, else, for ,while,{,}, +,-,* usw.
- Die Arbeit des Compilers besteht darin, mithilfe der lexikalischen Analyse und der Syntaxanalyse zu bestätigen, dass alle Anweisungen den grammatikalischen Regeln entsprechen, und sie dann in eine entsprechende Zwischencodedarstellung oder einen Assemblercode zu übersetzen.
- Die Optimierungsverarbeitung ist eine relativ schwierige Technologie im Kompilierungssystem. Die damit verbundenen Probleme hängen nicht nur mit der Kompilierungstechnologie selbst zusammen, sondern haben auch viel mit der Hardwareumgebung der Maschine zu tun. Ein Teil der Optimierung ist die Optimierung des Zwischencodes. Diese Optimierung ist unabhängig vom jeweiligen Rechner. Eine andere Art der Optimierung zielt hauptsächlich auf die Generierung von Zielcode ab.
- Bei der früheren Optimierung besteht die Hauptaufgabe darin, öffentliche Ausdrücke zu löschen, Schleifen zu optimieren (Code-Extraktion, Stärkeschwächung, Schleifensteuerungsbedingungen ändern, bekannte Mengen zusammenführen usw.), die Weitergabe von Kopien und das Löschen nutzloser Zuweisungen usw. Warten.
- Die letztgenannte Art der Optimierung hängt eng mit der Hardwarestruktur der Maschine zusammen. Das Wichtigste ist, wie die Werte der relevanten Variablen, die in jedem Hardwareregister der Maschine gespeichert sind, vollständig genutzt werden können, um die Anzahl zu reduzieren der Speicherzugriffe. Darüber hinaus ist es auch ein wichtiges Forschungsthema, wie einige Anpassungen an Anweisungen entsprechend den Eigenschaften von Maschinenhardware-Ausführungsanweisungen (wie Pipeline, RISC, CISC, VLIW usw.) vorgenommen werden können, um den Zielcode kürzer und die Ausführungseffizienz zu erhöhen .
Assembly: Assembly bezieht sich eigentlich auf den Prozess der Übersetzung von Assembler-Code in Zielmaschinenanweisungen. Für jedes vom Übersetzungssystem verarbeitete Quellprogramm in C-Sprache wird durch diese Verarbeitung schließlich die entsprechende Zieldatei erhalten. Was in der Zieldatei gespeichert ist, ist der Maschinensprachencode des Ziels, der dem Quellprogramm entspricht. Objektdateien bestehen aus Segmenten. Normalerweise gibt es in einer Objektdatei mindestens zwei Abschnitte:
- Codeabschnitt: Dieser Abschnitt enthält hauptsächlich die Anweisungen des Programms. Dieses Segment ist im Allgemeinen lesbar und ausführbar, jedoch im Allgemeinen nicht beschreibbar.
- Datensegment: Es speichert hauptsächlich verschiedene globale Variablen oder statische Daten, die im Programm verwendet werden. Im Allgemeinen sind Datensegmente lesbar, beschreibbar und ausführbar.
In der UNIX-Umgebung gibt es drei Haupttypen von Objektdateien:
- Verschiebbare Dateien: Enthält Code und Daten, die für die Verknüpfung mit anderen Objektdateien geeignet sind, um eine ausführbare oder gemeinsam genutzte Objektdatei zu erstellen.
- Freigegebene Objektdatei: In dieser Datei werden Code und Daten gespeichert, die für die Verknüpfung in zwei Kontexten geeignet sind. Der erste besteht darin, dass der Linker sie mit anderen verschiebbaren Dateien und gemeinsam genutzten Objektdateien verarbeiten kann, um eine weitere Objektdatei zu erstellen. Der zweite besteht darin, dass der dynamische Linker sie mit einer anderen ausführbaren Datei und anderen gemeinsam genutzten Objektdateien verarbeiten kann Bild.
- Ausführbare Datei: Sie enthält eine Datei, die von einem vom Betriebssystem erstellten Prozess ausgeführt werden kann. Was der Assembler generiert, ist tatsächlich die erste Art von Objektdatei. Für die beiden letztgenannten ist eine andere Verarbeitung erforderlich, um sie zu erhalten. Dies ist die Aufgabe des Linkers.
Verknüpfungsprozess:
Die vom Assembler generierte Objektdatei kann nicht sofort ausgeführt werden, und es können viele ungelöste Probleme auftreten.
- Zum Beispiel: Eine Funktion in einer Quelldatei kann auf ein Symbol verweisen, das in einer anderen Quelldatei definiert ist (z. B. eine Variable oder ein Funktionsaufruf usw.); eine Funktion in einer Bibliotheksdatei kann im Programm aufgerufen werden usw. Alle diese Probleme müssen vom Linker gelöst werden.
- Die Hauptaufgabe des Linkers besteht darin, verwandte Zieldateien miteinander zu verbinden, dh die in einer Datei referenzierten Symbole mit der Definition des Symbols in einer anderen Datei zu verbinden, sodass alle diese Zieldateien zu einer betriebsfähigen Einheit werden das Ganze, das das System lädt und ausführt.
Entsprechend den vom Entwickler angegebenen unterschiedlichen Verknüpfungsmethoden derselben Bibliotheksfunktion kann die Verknüpfungsverarbeitung in zwei Typen unterteilt werden:
- Statische Verknüpfung: Bei dieser Verknüpfungsmethode wird der Code der Funktion von heruntergeladen Statische Linkbibliothek, in der sie sich befindet. Kopiert in das endgültige ausführbare Programm. Auf diese Weise werden diese Codes bei der Ausführung des Programms in den virtuellen Adressraum des Prozesses geladen. Eine statische Linkbibliothek ist eigentlich eine Sammlung von Objektdateien, von denen jede den Code für eine oder eine Gruppe verwandter Funktionen in der Bibliothek enthält.
- Dynamische Verknüpfung: Bei dieser Methode wird der Code der Funktion in einer Objektdatei abgelegt, die als dynamische Linkbibliothek oder gemeinsam genutztes Objekt bezeichnet wird. Zu diesem Zeitpunkt zeichnet der Linker den Namen des gemeinsam genutzten Objekts und eine kleine Menge anderer Registrierungsinformationen im endgültigen ausführbaren Programm auf. Wenn diese ausführbare Datei ausgeführt wird, wird der gesamte Inhalt der Dynamic Link Library zur Laufzeit dem virtuellen Adressraum des entsprechenden Prozesses zugeordnet. Der dynamische Linker findet den entsprechenden Funktionscode basierend auf den im ausführbaren Programm aufgezeichneten Informationen.
Für Funktionsaufrufe in ausführbaren Dateien kann jeweils eine dynamische Verknüpfung oder eine statische Verknüpfung verwendet werden. Die Verwendung dynamischer Verknüpfungen kann die endgültige ausführbare Datei kürzer machen und etwas Speicher sparen, wenn ein gemeinsam genutztes Objekt von mehreren Prozessen verwendet wird, da nur eine Kopie des Codes für dieses gemeinsam genutzte Objekt im Speicher gespeichert werden muss. Dies bedeutet jedoch nicht unbedingt, dass die Verwendung dynamischer Links der Verwendung statischer Links überlegen ist. In einigen Fällen kann die dynamische Verknüpfung zu Leistungseinbußen führen.
Verwandte Empfehlungen: „C-Video-Tutorial“
Das obige ist der detaillierte Inhalt vonWelche Dateien werden nach dem Kompilieren der Quelldateien in C-Sprache generiert?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!