Zusammenfassung
Diese Lektion erklärt, was ein Programm ist und was ein Compiler tut: Die Maschine führt nur elementare Operationen mit Zahlen im Speicher aus, und der Quellcode muss in Maschinencode übersetzt werden, bevor er ausgeführt wird. An einem Programm aus zwei Dateien verfolgt sie die drei Phasen dieser Übersetzung in C++ (Präprozessorlauf, Kompilierung und Linken) und unterscheidet, was beim Kompilieren geschieht, von dem, was beim Ausführen geschieht, mit je einem Fehler jeder Art. Sie vergleicht kompilierte mit interpretierten Sprachen, fasst die Geschichte von C++ bis zu den Standards C++23 und C++26 zusammen und legt fest, welches C++ dieser Kurs lehrt.
Lernziele
- Mit einem Schema den Weg einer Quelldatei bis zu einer ausführbaren Datei erklären: Präprozessorlauf, Kompilierung und Linken, mit dem, was in jede Phase eingeht und aus ihr hervorgeht.
- Ein Ereignis der Kompilierung, dem Linken oder der Ausführung zuordnen, etwa einen Syntaxfehler, das Fehlen einer Funktion oder eine Division durch null.
- Eine kompilierte mit einer interpretierten Sprache vergleichen, nach dem Zeitpunkt, zu dem Fehler erkannt werden, und danach, was der Programmierer von der Maschine sieht.
- Die Standards der Sprache zeitlich einordnen und erklären, warum der Kurs C++23 verwendet.
Was ein Programm ist
Ein Programm ist eine Folge von Anweisungen, die eine Maschine ausführen kann, um Daten in Ergebnisse umzuwandeln. In der vorigen Lektion, Vom Problem zum Algorithmus, wurde gezeigt, dass vor dem Programm der Algorithmus steht, die Methode, die das Problem löst, so geschrieben, dass ein Mensch ihr folgen kann. Das Programm ist dieselbe Methode, geschrieben für eine Maschine, die weder Sätze noch Absichten versteht, sondern nur elementare Befehle: eine Zahl von einer Stelle des Speichers an eine andere kopieren, zwei Zahlen addieren, sie vergleichen, je nach Ergebnis zu einem anderen Befehl springen.
Der Speicher des Computers ist eine lange Reihe nummerierter Zellen, jede mit einer Zahl von 0 bis 255 (einem Byte). Alles, womit die Maschine umgeht, ist dort als Zahlen kodiert: die Daten und auch die Befehle selbst. Wenn dem so ist, dann ist ein ausführbares Programm eine Folge von Zahlen, die der Prozessor als Befehle deutet, der Maschinencode, und er hängt vom Prozessor ab: Die der Familie x86-64, verbreitet in Desktop-Computern, und die ARM-Prozessoren, verbreitet in Telefonen und in neueren Macs, verstehen nicht dieselben Zahlen.
Niemand schreibt heute in Maschinencode. Man schreibt in einer Hochsprache wie C++, deren Anweisungen der mathematischen Notation ähneln, in eine Textdatei, die Quellcode heißt (oder einfach Quelltext). Daraus folgt, dass ein Übersetzer nötig ist, der aus dem Quelltext den gleichwertigen Maschinencode erzeugt: in C++ der Compiler.
Dies ist ein vollständiges Programm in C++. Es muss noch nicht verstanden werden: Jede seiner Zeilen wird in Lektion 1.5 erklärt. Es genügt zu sehen, dass es Text ist, dass man es lesen kann und dass darunter steht, was es bei der Ausführung ausgegeben hat.
// Wird in Lektion 1.5 Zeile für Zeile erklärt.#include <print>int main(){ std::println("Hallo, Welt"); std::println("2 + 3 = {}", 2 + 3);}Hallo, Welt2 + 3 = 5Um das andere Ende der Übersetzung zu sehen, betrachten wir eine Funktion, die nur zwei ganze Zahlen addiert (Funktionen werden in Einheit 5 behandelt). Sie ist kein vollständiges Programm, weil ihr main fehlt, der Punkt, an dem die Ausführung beginnt, aber der Compiler kann sie für sich allein übersetzen.
int add(int a, int b){ return a + b;}Das Programm objdump zeigt den Maschinencode, den GCC 15 für sie auf einem x86-64-Computer mit Linux erzeugt hat: in jeder Zeile die Position des Befehls, seine Bytes in Hexadezimalschreibweise und seinen lesbaren Namen.
$ g++ -std=c++23 -O1 -c add.cpp$ objdump -d add.o0000000000000000 <_Z3addii>: 0: f3 0f 1e fa endbr64 4: 8d 04 37 lea (%rdi,%rsi,1),%eax 7: c3 retDie ganzzahlige Addition schrumpfte auf acht Bytes. Der erste Befehl, endbr64, ist eine Sicherheitsmarke, die nichts berechnet; der zweite, lea, addiert die beiden Zahlen, die die Funktion in zwei Registern des Prozessors erhalten hat (%rdi und %rsi), und legt das Ergebnis in einem dritten ab (%eax); der dritte, ret, kehrt zu der Stelle zurück, von der aus die Funktion aufgerufen wurde. Der Name _Z3addii ist die Art, wie der Compiler „Funktion add mit zwei Parametern vom Typ int“ kodiert.
Aus dem Beispiel ergeben sich zwei Beobachtungen. Erstens: Der Maschinencode bewahrt weder die Namen a und b noch die Einrückung noch die Kommentare; all das existierte für den, der den Quelltext liest, und der Compiler nutzte es, um zu entscheiden, welche Befehle er schreibt. Zweitens: Was der C++-Programmierer schreibt, wird in wenige Befehle und ohne Vermittler übersetzt; diese Nähe zur Maschine ist der Grund für die Effizienz von C++ und, wie der Kurs zeigen wird, auch für manche seiner Gefahren.
Inhalt wird geladen…
Abgeschlossen
Melde dich an, um deinen Fortschritt zu speichern