Neben den unbedingten Sprüngen gibt es – wenig überraschend – auch die bedingten Sprünge, die nur dann ausgeführt werden, wenn eine bestimmte Bedingung erfüllt ist. Auch hierzu ein diesmal etwas sinnvolleres Beispiel (die Erklärung folgt darunter, die Zeilen sind nummeriert):
(2) mov bl, var1
loop:
(3) sub bl, var2
(4) cmp bl, 0
(5) jl end
(6) inc al
(7) jmp loop
end:
(8) add bl, var2
Nehmen wir an, dass in den Variablen var1 und var2 zwei Werte gespeichert sind, wobei wir die Gleichung var1 / var2 berechnen wollen, und zwar ganzzahlig und mit Rest. In den ersten beiden Zeilen (1) und (2) des Codes wird das Register al mit dem Wert 0 und das Register bl mit dem Wert von var1 gefüllt. Anschließend folgt eine Sprungmarkierung. In der darauffolgenden Zeile (3) wird vom Wert in bl der Wert von var2 abgezogen. Anschließend erfolgt in (4) ein Vergleich (cmp von englisch “compare”) von bl und dem festen Wert 0. Dabei werden intern im Prozessor einige sogenannte Flags (separate Register mit einer Größe von nur einem Bit) gesetzt, je nachdem, ob der erste Wert größer, kleiner oder gleich dem zweiten Wert ist. Die Magie passiert in der nächsten Zeile (5): jl end ist ein bedingter Sprung (jl von englisch “jump less”), der in diesem Fall genau dann ausgeführt wird, wenn der vorhergehende Vergleich ergeben hat, dass der erste Wert kleiner als der zweite ist (für die Kenner: der Sprung wird durchgeführt, wenn das Zeroflag auf 0 und das Signflag ungleich dem Overflowflag ist). Ist dies nicht der Fall, wird in Zeile (6) der Wert im Register al um eins erhöht (inc von englisch “increment) und anschließend in (7) zurück zum Anfang der Schleife gesprungen. Wird der bedingte Sprung dagegen durchgeführt, wird noch die letzte Anweisung (8) hinter der Schleife ausgeführt, in welcher auf den Wert in Register bl noch einmal der Wert von var2 draufaddiert wird, um den korrekten Rest zu berechnen.
Eigentlich alles ganz klar, oder? Nein? Gut, hier eine kleine Wertetabelle für eine Beispielrechnung; nehmen wir an var1 hat den Wert 8 und var2 den Wert 3, wir wollen also 8/3 berechnen. Damit ergibt sich die folgende Tabelle; jede Zeile markiert einen Ausführungszyklus:
Codezeile | al | bl | Bemerkung |
---|---|---|---|
2 | 0 | 8 | Initialisierung |
3 | 0 | 5 | Berechnung von: bl – var2 |
4 | 0 | 5 | Vergleich: 5 < 0? |
5 | 0 | 5 | 5 ist nicht kleiner als 0, also passiert nichts |
6 | 1 | 5 | Inkrement: al |
7 | 1 | 5 | Sprung zurück zu “loop” |
3 | 1 | 2 | Berechnung von: bl – var2 |
4 | 1 | 2 | Vergleich: 2 < 0? |
5 | 1 | 2 | 2 ist nicht kleiner als 0, also passiert nichts |
6 | 2 | 2 | Inkrement: al |
7 | 2 | 2 | Sprung zurück zu “loop” |
2 | 2 | 2 | Berechnung von: bl – var2 |
4 | 2 | -1 | Vergleich: -1 < 0? |
5 | 2 | -1 | -1 ist kleiner als 0, also Sprung zur Markierung “end” |
8 | 2 | 2 | Addition: bl + var2 |
Und in der Tat: 8 geteilt durch 3 ergibt bei ganzzahliger Division 2, Rest 2. Mal ehrlich: so schwer war es am Ende nun doch nicht, oder? Mit ein wenig Konzentration und gutem Willen lässt sich Assemblercode doch relativ gut verstehen. Zumindest besser als reiner Maschinencode. Aber ich gebe gerne zu, dass auch Assemblercode nicht optimal ist; zumindest wird er den Anforderungen der modernen Programmierwelt definitiv nicht gerecht. Er bietet jedoch die Möglichkeit, auf einer sehr niedrigen Ebene Programme schreiben (und vor allem auch später wieder lesen) zu können und wird auch heutzutage noch ab und zu benutzt. Die hauptsächliche Programmierung von Computern wird aber mit sogenannten Hochsprachen durchgeführt – aber die schauen wir uns ein andermal an.
Kommentare (20)