logo

Kompilieren eines C-Programms: Hinter den Kulissen

Bei der Kompilierung handelt es sich um den Prozess der Konvertierung des Quellcodes der C-Sprache in Maschinencode. Da C eine Sprache mittlerer Ebene ist, benötigt sie einen Compiler, der sie in einen ausführbaren Code umwandelt, damit das Programm auf unserem Computer ausgeführt werden kann.

Das C-Programm durchläuft beim Kompilieren folgende Phasen:



Kompilierungsprozess in c

Kompilierungsprozess in C

Wie kompilieren und führen wir ein C-Programm aus?

Wir benötigen zunächst einen Compiler und einen Code-Editor, um ein C-Programm zu kompilieren und auszuführen. Das folgende Beispiel zeigt eine Ubuntu-Maschine mit GCC-Compiler.

Schritt 1: Erstellen einer C-Quelldatei

Wir erstellen zunächst ein C-Programm mit einem Editor und speichern die Datei unter Dateiname.c



SQL-Reihenfolge nach Datum
  $ vi filename.c>

Wir können ein einfaches Hallo-Welt-Programm schreiben und es speichern.

int-Zeichenfolge

Schritt 2: Kompilieren mit dem GCC-Compiler

Wir verwenden den folgenden Befehl im Terminal, um unsere Quelldatei filename.c zu kompilieren

  $ gcc filename.c –o filename>

Wir können viele Anweisungen an den GCC-Compiler für verschiedene Aufgaben übergeben, wie zum Beispiel:



  • Die Option -Wall aktiviert alle Warnmeldungen des Compilers. Diese Option wird empfohlen, um besseren Code zu generieren.
  • Mit der Option -o wird der Name der Ausgabedatei angegeben. Wenn wir diese Option nicht nutzen, wird eine Ausgabedatei mit dem Namen a.out generiert.

Wenn in unserem C-Programm keine Fehler vorliegen, wird die ausführbare Datei des C-Programms generiert.

Schritt 3: Ausführen des Programms

Nach der Kompilierung wird die ausführbare Datei generiert und wir führen die generierte ausführbare Datei mit dem folgenden Befehl aus.

  $ ./filename>

Das Programm wird ausgeführt und die Ausgabe wird im Terminal angezeigt.

Was passiert im Kompilierungsprozess?

Ein Compiler wandelt ein C-Programm in eine ausführbare Datei um. Es gibt vier Phasen, in denen ein C-Programm zu einer ausführbaren Datei wird:

spitzer Winkel
    Vorverarbeitung der Kompilierungs-Assembly-Verknüpfung

Durch Ausführen des folgenden Befehls erhalten wir alle Zwischendateien im aktuellen Verzeichnis zusammen mit der ausführbaren Datei.

  $gcc -Wall -save-temps filename.c –o filename>

Der folgende Screenshot zeigt alle generierten Zwischendateien.

Zwischendateien

Lassen Sie uns nacheinander sehen, was diese Zwischendateien enthalten.

1. Vorverarbeitung

Dies ist die erste Phase, die der Quellcode durchläuft. Diese Phase umfasst:

Folgedatentypen
  • Entfernung von Kommentaren
  • Erweiterung von Makros
  • Erweiterung der enthaltenen Dateien.
  • Bedingte Kompilierung

Die vorverarbeitete Ausgabe wird im gespeichert Dateiname.i . Mal sehen, was in filename.i: using enthalten ist $vi Dateiname.i

In der obigen Ausgabe ist die Quelldatei mit vielen, vielen Informationen gefüllt, aber am Ende bleibt unser Code erhalten.

  • printf enthält jetzt a + b statt add(a, b), weil Makros erweitert wurden.
  • Kommentare werden entfernt.
  • #include fehlt, stattdessen sehen wir viel Code. Daher wurden die Header-Dateien erweitert und in unsere Quelldatei aufgenommen.

2. Kompilieren

Der nächste Schritt besteht darin, filename.i zu kompilieren und ein; Zwischenkompilierte Ausgabedatei Dateiname.s . Diese Datei befindet sich in Anweisungen auf Baugruppenebene. Sehen wir uns diese Datei mit an $nano-Dateiname.s Terminalbefehl.

Assembly-Codedatei

Der Schnappschuss zeigt, dass es sich um eine Assemblersprache handelt, die der Assembler verstehen kann.

3. Zusammenbau

In dieser Phase wird der Dateiname.s als Eingabe verwendet und in umgewandelt Dateiname.o durch den Monteur. Diese Datei enthält Anweisungen auf Maschinenebene. In dieser Phase wird nur vorhandener Code in Maschinensprache konvertiert und Funktionsaufrufe wie printf() werden nicht aufgelöst. Sehen wir uns diese Datei mit an $vi Dateiname.o

Git-Status

Binärcode

4. Verlinkung

Dies ist die letzte Phase, in der die gesamte Verknüpfung von Funktionsaufrufen mit ihren Definitionen erfolgt. Linker weiß, wo all diese Funktionen implementiert sind. Linker erledigt auch einige zusätzliche Arbeit, er fügt unserem Programm zusätzlichen Code hinzu, der erforderlich ist, wenn das Programm startet und endet. Beispielsweise gibt es einen Code, der zum Einrichten der Umgebung wie der Übergabe von Befehlszeilenargumenten erforderlich ist. Diese Aufgabe kann mithilfe von leicht überprüft werden $size Dateiname.o Und $size-Dateiname . Durch diese Befehle wissen wir, wie die Ausgabedatei von einer Objektdatei zu einer ausführbaren Datei wird. Dies liegt an dem zusätzlichen Code, den Linker unserem Programm hinzufügt.

Notiz: GCC führt standardmäßig eine dynamische Verknüpfung durch, daher wird printf() im obigen Programm dynamisch verknüpft. Weitere Informationen zur statischen und dynamischen Verknüpfung finden Sie hier, hier und hier.