Der Unterschied zwischen einfachen und hohen Sprachen liegt nicht in der Logik des Programmierens oder dem Ausführen der Befehle, sondern daran, wie ich das Programm schreibe. Die Syntax und die Detailtiefe meiner Befehle an die Maschine spielen hier eine große Rolle. Ähnlich wie ich Fachbegriffe mit meinen Arbeitskollegen nutze, um technische Fragen zu klären, oder wie ich dagegen eine einfache Sprache wähle, wenn ich mich mit meinem fünfjährigen Sohn unterhalte.
Die einfachen Sprachen sind für die Maschinen einfach zu verstehen. Die Syntax ist für uns Menschen etwas ungewöhnlich, aber für die Maschine sind es klare Befehle, mehrheitlich Arithmetik und Speicherzugriffe. Mit Arithmetik meine ich hier die vier Grundrechenarten, also Addieren, Subtrahieren, Multiplizieren und Dividieren. Alle anderen Funktionen, zum Beispiel eine Potenz, bei der man »x hoch y« rechnet, lassen sich als Kombinationen dieser Grundoperation genannten Grundrechenarten beschreiben. Diese Funktionen werden in Bibliotheken gespeichert, die man dann abrufen kann und sich so den Aufwand spart, jedes Mal die ganze Funktion neu beschreiben zu müssen. Man muss sich das so vorstellen, als hätte die Maschine mehrere Schränke mit vielen Schubladen drin, einen Hauptschrank für die Grundoperationen und einen für die Bibliothek voller Funktionen, in jeder Schublade eine. Mit meinen Befehlen sage ich: »Öffne mal die oberste Schublade von links, lies den Inhalt heraus, dann öffne die dritte Schublade von unten, summiere die Inhalte von beiden Schubladen, und stecke das Ergebnis in die vierte Schublade oben rechts.«
Die einfachen Sprachen nennt man auch »Assemblersprachen«. Damit die Maschine genau das erledigt, was im Code steht, muss man die Anweisungen sehr präzise beschreiben, es müssen also alle Schritte genau aufgelistet werden, und zwar in der richtigen Reihenfolge, ähnlich wie bei einem Kochrezept für jemanden, der nie kocht. Dafür muss ich die Architektur meines Systems kennen und die Funktion, die jedes Element erfüllen soll, verstanden haben – nicht unbedingt bis ins kleinste Detail, aber es ist auf jeden Fall hilfreich, die grobe Architektur eines Computers zu kennen, so wie man einigermaßen Bescheid wissen sollte, in welchen Küchenutensilien man welche Zutaten mischen, garen oder braten kann. Die Architektur eines Computers besteht hauptsächlich aus folgenden Elementen:
Der Prozessor bildet die zentrale Verarbeitungseinheit eines Computers, daher die englische Abkürzung CPU für Central Processing Unit. Er ist für die zentrale Steuerung zuständig (also etwa der Küchenchef). Er bekommt die Befehle (Bestellungen) und lässt diese durch die angeschlossenen Maschinen durchführen. Der Prozessor verfügt über:ein Rechenwerk, das Rechenoperationen und logische Verknüpfungen durchführt. Das Rechenwerk wird im Englischen ALU genannt, für Arithmetic Logic Unit (so etwas wie alle Küchengeräte zusammen);und ein Steuerwerk, das die Anweisungen eines Programms, also die Befehle, interpretiert und die notwendigen Verschaltungen für Datenquelle (Ursprung der Daten), Datensenke (Empfänger oder Bestimmungsort der Daten) und ALU-Komponenten durchführt. Es regelt auch die Befehlsabfolge, nach dem Motto »Ordnung muss sein!« (so etwas wie der Chef vom Dienst).
Das Bus-System stellt eine Kommunikationsautobahn dar und dient zur Kommunikation zwischen allen Komponenten (wie das Bestellsystem im Restaurant beziehungsweise Kinderzimmer).
Das Speicherwerk dient der Speicherung aller Programme und Daten, auch Arbeitsspeicher genannt (sämtliche Rezeptbücher und Tupperdosen für Mehl, Zucker etc.).
Das Ein- und Ausgabewerk steuert, wie der Name schon sagt, die Ein- und Ausgabe von Daten an den Anwender über Tastatur und Bildschirm, und zu anderen Systemen über Schnittstellen (ungefähr so wie die Teller, auf denen das leckere Essen serviert wird).1
Die einfache Darstellung in Abbildung 4 zeigt die Hauptkomponenten eines Rechners. Natürlich kann ein Computer mehrere Prozessoren und mehrere Speicher haben, aber mir geht es hier um das grobe Bild und Verständnis der Funktionen, die jede Komponente erfüllen muss.
Das Coden lässt sich auch mit einem Umzug, den man in Auftrag gibt, vergleichen. Stellt euch vor, ich möchte in eine neue Wohnung umziehen, packe alles in Kisten, beschrifte sie und beauftrage eine Umzugsfirma. Ich möchte nicht nur, dass sie mir die Kisten in die jeweiligen Räume verteilt werden und die Möbel wieder aufgebaut werden.
Dafür muss ich wissen, welche Räume die neue Wohnung hat, ich muss die Funktion jedes Raums kennen und muss planen, welche Räume zuerst eingerichtet werden sollen, damit der Einzug reibungslos ablaufen kann.
Abbildung 4: Hauptkomponenten eines Rechners
Wenn ich diesen Plan habe, also die Architektur der Wohnung und die Funktion der Räume kenne, kann ich der Umzugsfirma genau sagen, welche Kiste wohin soll (also die Speicherzugriffe festlegen). Außerdem kann ich sagen: »Im Schlafzimmer bitte das Bett und den Schrank direkt aufbauen (also Rechenaufgaben durchführen lassen), in der Küche bitte den Küchentisch und die Stühle sowie im Arbeitszimmer alle restlichen Kisten erst einmal stapeln (also Zwischenspeicher verwenden), da ich sie erst nach und nach ausräumen möchte.« Ich kann auch sagen: »Im Wohnzimmer darf nichts zusammengebaut oder gesägt werden, da dort ein Teppich liegt (Registereinschränkungen beachten). Das muss alles woanders erledigt und dann ins Wohnzimmer getragen werden.« Oder ich kann meine Sommerklamotten in die erste Schublade von links im neuen Kleiderschrank sortieren lassen (wieder Speicherzugriffe). Am besten klappt das natürlich, wenn ich das genaue Volumen meiner Sommerklamotten kenne und weiß, dass genug Platz dafür in der ersten Schublade von links vorhanden ist (also die Größe meiner Variablen in Bytes und die Speicherplatzkapazität).
Programmieren ist also so ähnlich wie Kochen oder Umziehen. Ich befehle einer Maschine mit dem Code, den ich schreibe, etwas zu tun, aber ich muss auch wissen, wo sie etwas rechnen, speichern, zwischenspeichern und ausgeben kann. Je einfacher die Programmiersprache, desto detaillierter meine Anweisungen an die Maschine. Bei hohen Sprachen ist das etwas anders, aber den Unterschied sehen wir uns gleich noch anhand eines konkreten Beispiels an.
Um mit Programmiersprachen klarzukommen, sollte man etwas Englisch verstehen, denn alle Befehle sind in English. Wenn man mit Englisch noch nichts am Hut hatte, steht man vor der zusätzlichen Herausforderung, alle Befehle aus dem Deutschen beziehungsweise ins Deutsche übersetzen zu müssen. Aber auch das kriegt man schnell hin.
Assembler: Maschinensprech für Anfänger
Assemblersprachen werden verwendet, um Betriebssysteme zu programmieren und sind daher systemspezifisch. Sie hängen also von dem speziellen Betriebssystem und dessen Komponenten ab. Wie bereits erwähnt, kommen Assemblersprachen eher aus der Perspektive der Maschinen, das heißt, ihr kommt damit dem Kern des Systems sehr nah und solltet ihn deshalb auch möglichst gut kennen. Vor dem Programmieren solltet ihr also wissen, welche Hauptkomponenten euer System hat und, vor allem, welche konkrete CPU. Denn je nach CPU, kommen andere Befehle ins Spiel. Eine der erfolgreichsten CPU-Architekturen und der damit verbundenen Befehlssätze ist die x86-Befehlsatzarchitektur, kurz x86-Architektur. Sie wurde für die erste x86-Prozessoren in den Achtzigerjahren entwickelt und kommt bis heute zum Einsatz.
Und jetzt machen wir eine erste Sprachübung: Im Assembler ist der Befehl »mov« – vom englischen »move«, also bewegen – einer der am häufigsten verwendeten Befehle. Anders als bei einem Umzug bewegt die Maschine in Wirklichkeit nichts, sie kopiert nur Werte hin und her, bewegt die Umzugskisten also nur virtuell. Dazu gehört folgende Syntax:
mov op1, op2
Damit befehlen wir dem Prozessor, den Wert von op2 (zweiter Operand oder Quelloperand) in op1 (erster Operand oder Zieloperand) zu kopieren. Operand ist hier nur ein anderes Wort für Variable, in unserem Beispiel könnte das die Schublade für die Sommerklamotten sein, die geöffnet werden muss. Damit das auch klappt, müssen wir sicherstellen, dass beide Operanden dieselbe Größe an Bytes haben. Außerdem müssen wir der Maschine sagen, »wo« sie die Befehle ausführen soll, »wohin« sie das Ergebnis speichern soll und »woher« sie die Werte bekommt. Es gibt also Speicherplätze, Datensegmente und Register.