Zur Hauptnavigation springen [Alt]+[0] Zum Seiteninhalt springen [Alt]+[1]

Programmierung: Hochsprache, Assembler und Maschinensprache

Da sich die Simulation der Registermaschine am von-Neumann-Rechner orientiert, liegen Befehle und Daten im Arbeitsspeicher. Sie sind also binär codiert, also als Folge von Nullen oder Einsen abgelegt. Diese Darstellung ist für den Menschen nicht lesbar, die Maschine kann diese Zeichenfolgen aber interpretieren. Diese Interpretation basiert auf der sogenannten Maschinensprache.

Die Maschinensprache bezeichnet ein System von Instruktionen, die der jeweilige Prozessor direkt ausführen kann. Vereinfacht gesagt sind der Umfang und die Ausprägung des Instruktionssatzes von den im Prozessor umgesetzten Schaltnetzen abhängig. Jede zur Verfügung stehende Instruktion ist durch eine festgelegte Bitfolge von Nullen und Einsen ansprechbar. Diese legt den Einsprungspunkt im Mikroprogrammspeicher fest.

Zum besseren Umgang mit der Maschinensprache werden die zulässigen Bitfolgen, die Maschinenbefehle, Adressen usw. mit symbolischen, für den Menschen verständlichen Namen belegt. Diese Befehle werden Assemblerbefehle genannt; sie ermöglichen die Implementierung von Assemblerprogrammen.

Mit einem speziellen prozessorspezifischen Werkzeug, dem Assembler, kann ein Assemblerprogramm dann meist nahezu eins zu eins in ein entsprechendes Maschinenprogramm umgewandelt werden.

Beispiel:

Assemblerbefehl Maschinencode
Operation Operand
MOV AX,[20] 00010000 00100000
SUB AX,[21] 00110100 00100001
JS 12 01100101 00010010
DEC AX 01011110 00000000

In Assembler oder gar Maschinensprache programmiert heute fast niemand mehr, da diese Programme sehr unübersichtlich und sehr schlecht auf anderen Computertypen übertragbar sind. Lediglich bei sehr zeitkritischen Anwendungen kann dies noch sinnvoll sein. Daher verwenden wir Hochsprachen, die rechnerunabhängiger sind, wesentlich komplexere Befehle und Möglichkeiten zur Strukturierung des Programmcodes besitzen (z.B. Objektorientierte Programmierung).

Um das Programm auf einem Computer ausführen zu können, muss es aber in Maschinensprache übersetzt werden. Diese Aufgabe haben Compiler und Interpreter. Ein Compiler übersetzt das Programm, sobald der Quelltext fertiggestellt ist. Danach liegt ein ausführbares Programm (bei Windows: z.B. exe-File) vor, das Maschinenbefehle enthält. Ein Interpreter übersetzt das Programm erst dann, wenn es benötigt wird. Im Extremfall wird jeder einzelne Befehl erst übersetzt und dann ausgeführt. Dies verlangsamt die Ausführung erheblich.

Sprachen mit Compiler sind beispielsweise Delphi und C++. Bekannte Interpretersprachen sind Basic oder PHP. Java stellt eine Zwischenlösung dar. Java-Programme werden von einem Compiler in Java-Byte-Code übersetzt (class- bzw. jar-File). Dieser wird dann bei der Ausführung von einem Interpreter (Java Virtual Machine = Java Runtime Enviroment JRE) in Maschinenbefehle übersetzt und ausgeführt. Da Java-Programme erst zur Laufzeit in Maschinenbefehle übersetzt werden, sind sie im Prinzip auf jedem Rechner lauffähig für den es die JRE gibt. Dafür sind sie aber langsamer als exe-Files.

Da die Registermaschine in Bezug auf die Berechenbarkeit genauso mächtig ist, wie ein realer Computer, müssen sich Programme in einer Hochsprache in Programme für eine Registermaschine übersetzen lassen. Für einen konkreten Simulator ist das aufgrund von Beschränkung des Speicherplatzes und der Registerbreite zumindest eingeschränkt möglich.

Variablen:

Variablen werden im RAM gespeichert. Bei der Deklaration der Variable muss festgelegt werden, an welcher Stelle im RAM sie gespeichert wird. Jeder Zugriff auf eine Variable wird dann in eine Assembleranweisung mit Adressangabe (z.B. MOV AX, [30], wenn die Variable an RAM-Stelle 30 steht) übersetzt. Arrays lassen sich dadurch realisieren, dass die Adresse des ersten Arrayelements in ein Register (z.B. BX) übertragen, der Index des benötigten Elements dazu addiert und dann ein Befehl wie MOV AX,[BX] verwendet wird.

Zuweisungen:

Um Variablen neue Werte zuzuweisen, müssen die neuen Werte zunächst in einem Register (meist AX) vorliegen. Dort werden gegebenenfalls auch Berechnungen durchgeführt. Danach wird das Ergebnis an die Speicherstelle der Variablen im Arbeitsspeicher übertragen.

Sollen komplexe Terme berechnet werden, ist es sinnvoll mit einem Stack zu arbeiten. Ein Stack ist ein Speicherbereich, für den es im Wesentlichen zwei Operationen gibt: PUSH legt eine neue Zahl auf den Stapel, POP holt die oberste Zahl vom Stapel und entfernt sie dort. Wandelt man einen Term in die Postfix-Notation um, lässt er sich gut mit einem Stapel berechnen. Die Berechnung erfolgt streng von links nach rechts, da man ohne Klammern auskommt und auch Punkt vor Strich schon berücksichtigt ist.

z.B. aus 3 * 5 + (3 – 2) wird 3 5 * 3 2 - +

Die Operanden werden zunächst auf den Stapel gelegt und immer wenn eine Rechenoperation durchgeführt werden soll, werden die obersten beiden Zahlen dem Stapel entnommen, damit die Berechnung durchgeführt und das Ergebnis wieder auf dem Stapel abgelegt.

z.B. 3 und 5 werden auf den Stapel gelegt, dann die Multiplikation ausgeführt und 15 wieder auf den Stapel gelegt. Dazu kommen 3 und 2 auf den Stapel. Danach wird die Subtraktion mit 3 und 2 ausgeführt und das Ergebnis 1 auf den Stapel gelegt. Am Ende werden die 15 und die 1 vom Stapel entnommen, addiert und das Ergebnis 16 auf den Stapel gelegt.

Entscheidungen:

Ablauf

Der Vergleich zweier Variablen/Werte wird durch eine Subtraktion realisiert. Je nachdem, ob das Ergebnis positiv, negativ oder gleich Null ist, kann entschieden werden, welche Zahl größer war. Durch einen bedingten Sprung wird dann ggf. der IF-Teil übersprungen. Falls es einen ELSE-Teil gibt, muss dieser nach der Ausführung des IF-Teils übersprungen werden.

Bedingungen mit „größer“ oder „kleiner“ werden dabei mit einem JS, bzw. JNS realisiert, Bedingungen mit „gleich“ durch ein JZ oder JNZ. „größer-gleich“ bzw. „kleiner-gleich“ werden in „nicht kleiner“ oder „nicht größer“ umgewandelt.

Schleifen:

Ablauf

While-Schleifen lassen sich ähnlich wie Entscheidungen realisieren. Zunächst wird die While-Bedingung überprüft. Ist sie nicht erfüllt, erfolgt ein (bedingter) Sprung hinter die Schleife. Andernfalls werden die Anweisungen, die wiederholt werden sollen, ausgeführt. Danach erfolgt ein unbedingter Sprung zur erneuten Überprüfung der Bedingungen.

For-Schleifen lassen sich in While-Schleifen umschreiben. Dazu muss der Initialisierungsteil als Anweisung vor der Schleife ausgeführt werden. Die Erhöhung der Zählvariablen wird vor dem unbedingten Sprung durchgeführt.

Damit ist der Weg vom Mikroprozessor zur Hochsprache, bzw. umgekehrt vollständig. Ein Computerprogramm einer Hochsprache wird von einem automatischen Compiler in Maschinencode übersetzt. Dieser wird von einem Computer mit von-Neumann Architektur verarbeitet, indem Befehl für Befehl aus dem Arbeitsspeicher geladen und mit Hilfe des Mikroprogrammspeichers in Steuersignale für den Prozessor übersetzt wird. Der Prozessor besteht aus elektronischen Schaltnetzen. Durch Aktivierung der Steuersignale wird das richtige Schaltnetz ausgewählt und die Berechnung durchgeführt. Die Schaltnetze sind aus einfachen logischen Bauelementen aufgebaut, die ihrerseits aus wenigen CMOS-Feldeffekttransistoren bestehen. Ein Programm in einer Hochsprache wird im Endeffekt durch elektrische Signale in einer Vielzahl von Transistoren ausgeführt.

 

 

Hintergrundinformationen: Herunterladen [odt][4 MB]

 

Weiter zu Literaturhinweise