Assembler-Beispiel 2


Ausgabe "Hello World" 10 mal auf den Bildschirm

    So sollte es auf dem Bildschirm aussehen:
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Das ist natürlich ganz einfach (denkt man), da muss man 10 Ausgaben organisieren. Das ist fast richtig, aber was macht man, wenn das 100 mal oder 1000 mal passieren soll?
So eine alte Programmierregel ist:
"Wenn man etwas mehr als zweimal schreiben muss, dann macht man dafür eine Schleife oder ein Unterprogramm!"

Ersteres nehmen wir nun in Angriff.

    So etwa sähe die Lösung in einer höheren Programmiersprache aus.
Man vereinbart eine Zählvariable, in diesem Fall i, macht eine Ausgabe, bzw. die Aktion die wiederholt werden soll, ehöht die Zählvariable um 1 und fragt dann ab, ob der Wert der Endwert, in diesem Fall 10, ist.
Hat sie noch nicht den Endwert, geht es wieder zurück zur Ausgabe, ist der Endwert erreicht, wird auch die Schleife beendet.

So weit, so gut!
Der Assembler hat nur leider keine if-Anweisung, man kann lediglich Flags auswerten. Man muss also genau definierte Bedingungen schaffen, um dann eine Entscheidung zur weiteren Bearbeitung treffen zu können.
Flags waren z.B. Überlauf, Übertrag, Leer oder negativ. Man muss Operationen durchführen, die genau wenn 10 erreicht ist ein Flag setzen. Z.B. subtrahiert man von i immer 10 ab. Dann gibt es sogar 2 Flags die bei 10 aussagekräftig sind, denn das Ergebnis der Subtraktion ist bei (i-10) mit i=10 nicht mehr negativ und es ist Null, d.b. S-Flag ist nicht mehr gesetzt (war SF=1 ist nun 0) und das Z-Flag wird gesetzt (ZF=1).

    Einen Zwischenschritt kann man sparen, man zählt gleich rückwärts. Dazu wird i=10 gesetzt und in jedem Durchlauf um 1 reduziert. Dazu benutzt man den SUB-Befehl, der setzt immer das Z-Flag. Der nächste Befehl muss dann ohne andere Zwischenaktionen die Auswertung des Flags sein.
Der Befehl heißt: JZ (Jump wenn Z gesetzt) oder JNZ (Jump Not Zero - Z=0).
Der Befehl vereinbart außerdem eine Marke, also z.B. JPZ m1, es wird, wenn die Bedingung zutrifft (das Flag ist nicht gesetzt), ein Sprung zur angegebenen Marke ausgeführt, anderenfalls wird der nächste Befehl ausgeführt, es gibt so unterschiedliche Stellen zu weiteren Abarbeitung des Programms.

    Nun zum Assembler-Programm:
Es soll gleich die LST-Datei zur Beschreibung genutzt werden.
Eine Änderung ist gleich am Anfang sichtbar, es wird nun das SMALL-Speichermodell verwendet (ein Code- und ein Datensegment).
Mit .DATA wird das Datensegment eröffnet und als nächstes folgt sofort unsere bekannte Zeichenkette.
Mit .CODE wird das Datensegment geschlossen und das Codesegment eröffnet.
Die erste beiden Befehle ordnen nun die vom System vergebene Adresse des Datensegments dem Register DS zu.
Nun kann das Programm beginnen. Dazu wird als erstes ein Wert auf 10 gesetzt. Gegenüber unserem kleinen Prozessor "ERNA" haben wir nun den Vorteil auf weitere interne Register des Prozessors zugreifen zu können, wir brauchen den Speicher nicht. Und man muss weiter überlegen, wieviel Bit man braucht. Da 8 Bit einen Wert bis zu 255 aufnehmen können, brauchen wir nur ein halbes Register, wir nehmen BL, den unteren Teil vom B-Register, also MOV BL,10. Man könnte auch MOV BL,0Ah schreiben, dez 10 hex geschrieben.
Nach dem PAP kommt die Ausgabe, aber zuvor soll es hierher zurückgehen. Man kann eine Marke direkt auf den ersten Befehl der Ausgabe setzen, aber es geht auch, dass ein sinnloser Befehl (einer der nichts macht) für die Marke genutzt wird, NOP heißt er (NO oPeration). Als Marke schreiben wir davor "m1:".
Dann folgt wieder die bekannte Ausgabe, dann der SUB-Befehl und sofort danach die Abfrage des Zero-Flags. Ist es nicht gesetzt, geht es zur Marke m1, anderenfalls zum nächsten Befehl und damit in bekannter Weise zum Ende des Programms.

    Na ja, so wollten wir es aber nicht!

Wenn man einmal die "!" zählt, kann man auch 10 zählen, die Schleife hat wohl schon funktioniert, aber die Darstellung entspricht nicht unseren Vorstellungen - was tun?

Wir brauchen einen Zeilenumbruch in der Ausgabe.

Die Schreibmaschine ist noch immer das Vorbild. Es kann zwar noch kaum Einer mit dem Ding arbeiten, aber diese Steuerung bezieht sich darauf!
Es gibt einen Hebel, der, wenn man ihn vorsichtig betätigt, den Wagen wieder zurückfährt, man schreibt wieder vom linken Rand. Drückt man kräftiger weiter, dreht auch noch die Walze ein wenig weiter, wir kommen nun auch noch in eine neue Zeile.
Es werden zwei Funktionen ausgeführt:


Die beiden Teile gibt es so eben auch noch in der Rechentechnik (werden so genutzt):
Leider waren sich die Hersteller der Betriebssystem nicht einig, was verwendet werden soll. Sehr übel ist, dass z.B. UNIX nur das LF braucht (das andere erfolgt automatisch), WINDOWS aber CR und LF nutzt. Quellen sind teilweise deshalb nicht kompatibel!

BetriebssystemZeichensatzAbkürzung Code HexCode DezimalEscape-Sequenz
Unix, Linux, Mac OS X, AmigaOS, BSD, weitereASCII LF0A10\n
Windows, DOS, OS/2, CP/M, TOS (Atari)ASCIICR LF 0D 0A13 10\r\n
Mac OS bis Version 9, Apple IIASCIICR0D13\r
AIX OS & OS/390EBCDICNEL1521

Bei der Schreibmaschine gibt es einen extra Hebel für den Zeilenumbruch, in Assembler aber leider keinen extra Befehl!
Dafür hat man die ASCII-Zeichen unter 20h reserviert. Wenn entsprechende Zeichen (in diesem Fall 0Ah oder 0Dh) auf dem Ausgabegerät ausgegeben werden, muss das Gerät diese als Steuerzeichen erkennen und eine entsprechende Reaktion am Gerät auslösen.
Dabei ist durchaus nicht garantiert, dass auch jedes Gerät das gleiche macht, also diese ASCII-Zeichen nur gezielt einsetzen!
Wenn man also einen Vorschub haben will, muss das mit im Ausgabetext vorhanden sein. Wer schon in einer höheren Programmiersprache gearbeitet hat, kennt das, z.B. verwendet PASCAL writeln oder C printf(" \n", ); , da braucht man modifizierte Befehle, bzw. C schreibt das auch in die Zeichenkette.

Nun zu unserem Assembler-Programm
Man muss irgendwie in die Zeichenkette vor dem "$" ein "0Ah" kriegen:
    text DB "hello world!$"
Das kann man, indem ein einzelnes Zeichen mit Komma eingefügt wird:
    text DB "hello world!",0Ah,"$"
Wieder übersetzen und ansehen:


Das war's auch nicht!
Aber 0Ah heißt nun mal LF (line feed) und das hat das System auch genau an der angegebenen Stelle ausgeführt.
Probieren wir doch einmal das andere Zeichen 0Dh:
    text DB "hello world!",0Dh,"$"
Das ist das Ergebnis:


Das schein ja nun völlig falsch zu sein, selbst die Schleife funktioniert nicht - falsch, es ist alles richtig. Schaut man genau hin, steht der Kursor an der ersten Position, es wurde also ordnungsgemäß das CR (carriage return) ausgeführt, die 10 Ausgaben stehen alle an der selben Stelle.
Offensichtlich müssen wir beide Zeichen nutzen
    text DB "hello world!",0Ah,0Dh,"$"
Und siehe da, es geht!

    Es soll nicht verschwiegen werden, dass der 8086 auch einen einfachen Schleifenbefehl hat: loop Marke. Von der Marke bis zu diesem Befehl wird alles was dazwischen steht pausenlos wiederholt, man braucht also auch eine Zählgröße, damit das nicht unendlich wird. Vor der Marke muss auf CX (16Bit) die Anzahl der Durchläufe vereinbart werden:
   ...
   mov   cx,10
m1:   nop
   ...
   loop   m1
   ...

Man spart intern den SUB-Befehl, man braucht aber ein 8Bit-Register mehr.

Im Beispiel 3 wollen wir noch weitere Schleifen in das Programm einbringen.

zurück zur Start-Seite (Beispiele)   /   weiter Beispiel 3
zurück zur Start-Seite