Der Fehler kam ganz am Ende
Das neue Kassenprogramm einer Bäckereikette lässt sich nicht bauen. Die Meldung lautet undefined reference to printf. Eine Kollegin liest daraufhin den C-Code Zeile für Zeile durch.
Der Code ist in Ordnung. Die Meldung stammt aus dem letzten Schritt des Builds, und der liest den Code gar nicht. Wer die Schritte kennt, sucht an der richtigen Stelle.
Am Ende dieser Lektion
Notiz
Ich kann die Phasen nennen, die ein Compiler mit einem Programm durchläuft.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Compilertreiber | ein Programm, das die anderen Werkzeuge der Reihe nach startet | gcc |
| Präprozessor | die Phase, die Dateien einfügt und Makros ersetzt | |
| Assembler | das Programm, das Assemblercode in Maschinencode übersetzt | as |
| Objektdatei | Maschinencode, der noch kein ganzes Programm ist | hello.o |
| Symbol | ein Name, den der Linker finden muss, etwa eine Funktion | |
| Linker | das Programm, das Objektdateien und Bibliotheken verbindet | ld |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
gcc startet andere Programme
Wenn Sie gcc hello.c tippen, übersetzt gcc selbst fast nichts. gcc ist ein Compilertreiber. Er startet andere Programme nacheinander und reicht die Datei des einen an das nächste weiter.
Mit der Option -### gibt gcc diese Befehle nur aus und führt keinen davon aus. Die zwei Befehle unten zeigen davon nur das Programm, mit dem jeder Befehl beginnt. Vergleichen Sie die beiden Listen.
In den Ordner compiler wechseln
cd ~/compilerHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Was gcc für -c starten würde
gcc -### -c hello.c 2>&1 | grep "^ " | cut -d " " -f 2Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Was gcc für ein ganzes Programm startet
gcc -### -o hello hello.c 2>&1 | grep "^ " | cut -d " " -f 2Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Beispiel: drei Programme, vier Phasen
| Programm | Phasen | Ergebnis |
|---|---|---|
| cc1 | Vorverarbeiten, dann kompilieren | Assemblercode, eine .s-Datei |
| as | Assemblieren | Eine Objektdatei, .o |
| collect2 | Binden, indem es ld startet | Das fertige Programm |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Die Datei jeder Phase behalten
Normalerweise landen die Zwischendateien in einem temporären Ordner und werden danach gelöscht. Die Option -save-temps legt sie neben den Quelltext.
Ein Build hinterlässt dann vier Dateien: hello.i nach dem Vorverarbeiten, hello.s nach dem Kompilieren, hello.o nach dem Assemblieren und hello nach dem Binden. Der Befehl file liest den Anfang jeder Datei und nennt ihre Art.
Einmal bauen, alles behalten
gcc -save-temps -o hello hello.cHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Welche Art Datei ist jede?
file hello.i hello.s hello.o helloHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Das Ende des vorverarbeiteten Codes
gcc -E hello.c | tail -n 4Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Beispiel: was file gesagt hat
| Datei | Erzeugt von | Was file meldet |
|---|---|---|
| hello.i | dem Präprozessor | C source, ASCII text |
| hello.s | dem Compiler | assembler source, ASCII text |
| hello.o | dem Assembler | ELF 64-bit LSB relocatable |
| hello | dem Linker | ELF 64-bit LSB pie executable |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Eine Objektdatei hat Lücken
hello.o enthält den Maschinencode für square und main. Aber main ruft printf auf, und printf steht nicht in dieser Datei. Es liegt in der C-Bibliothek.
nm listet die Symbole einer Objektdatei. T heißt: Der Code ist hier. U heißt undefiniert, also eine Lücke für den Linker.
Starten Sie danach den Linker ld selbst, ohne C-Bibliothek. Lesen Sie, was er meldet.
Die Symbole in hello.o
nm hello.oHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Ohne Bibliothek binden
ld hello.o -o kaputtHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Ein häufiger Fehler
Warnung
Viele halten jeden Build-Fehler für einen Fehler im eigenen Code. Ein undefined reference meldet der Linker, nachdem der Code sauber kompiliert wurde. Dem letzten Schritt fehlt der Code eines Symbols. Prüfen Sie also, was gebunden wird, nicht die Zeile in C.
Woher printf kommt
ldd helloHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Weiterführend
Tippen Sie diese Befehle selbst. Erzeugen Sie mit sed 's/square(7)/doppelt(7)/' hello.c > doppelt.c eine Kopie, in der eine Funktion doppelt aufgerufen wird, die es nirgends gibt. Bauen Sie mit gcc -c doppelt.c und sagen Sie, welche Phase sich meldet.
Fügen Sie dann über main die Zeile int doppelt(int n); ein. Bauen Sie erneut mit gcc -c, lesen Sie nm doppelt.o und bauen Sie zuletzt mit gcc doppelt.c. Erklären Sie, warum die zwei Fehler aus verschiedenen Phasen kommen.