CODINGLISTINGSGAMESLIST ★ HEUTE: THE BASIC STRING ORCHESTRA & THE ASSEMBLER PUNKS|CPC AMSTRAD INTERNATIONAL) ★

Die Assemblerecke (CPC Amstrad International '89/3)
★ Ce texte vous est présenté dans sa version originale ★ 
 ★ This text is presented to you in its original version ★ 
 ★ Este texto se presenta en su versión original ★ 
 ★ Dieser Text wird in seiner Originalfassung präsentiert ★ 

Gepflegte Geigenklänge und Stakkato-Hardrock unter einen Hut zu bringen, erfordert ohne Zweifel einen geschickten Arrangeur. Vor einer ähnlichen Aufgabe steht mitunter der CPC-Programmierer, wenn er heiße Assembler-Rhythmen in biederes BASIC-Barock einbinden will. Als Schnittstelle zwischen den beiden Sprachebenen dienen im allgemeinen CALL-oder RSX-Befehle, die es erlauben, Werte oder Variablen an ein Maschinenprogramm zur Bearbeitung weiterzureichen. Diese Folge der Assemblerecke soll zeigen, welche Feinheiten bei der Parameter Übergabe zu beachten sind und wie man in Assembler auf den relativ komplexen Datentyp 'String' zugreift..

Ein unbestreitbarer Vorteil der Programmiersprache BASIC ist die äußerst flexible Handhabung von Zeichenketten (Strings). Will man zum Beispiel in Turbo Pascal mit Textvariablen operieren, so müssen diese am Anfang des Programms deklariert werden, wobei es dem Programmierer obliegt, den notwendigen Speicherplatz abzuschätzen. Das sieht etwa so aus:

VAR Name: STRING[20]

Die Variable 'Name' kann danach maximal 20 Zeichen aufnehmen. Die Länge dieser 'statischen' Strings läßt sich während des Programmablaufs nicht mehr ändern, auch wenn zufällig ein Name mit mehr als 20 Buchstaben auftaucht.

Wohin mit all den netten Zeichenketten?

Der BASIC-Interpreter des CPC pflegt dagegen seine Strings dynamisch zu verwalten: Jeder Zeichenkette wird exakt der Platz im Speicher eingeräumt, den sie gerade benötigt, ohne daß sich der Programmierer irgendwelche Gedanken darum zu machen braucht. Wer in Assembler auf BASIC-Strings zugreifen will, muß sich jedoch wohl oder übel damit beschäftigen, wie der Interpreter dieses Kunststück vollbringt. Ein kurzes Beispiel verdeutlicht sofort, daß es sich hierbei keinesfalls um ein triviales Problem handelt. Nach der Zuweisung

name$ = "Otto"

werden irgendwo im Speicher vier Bytes reserviert, um die ASCII-Nummern der vier Buchstaben abzulegen. Ein paar Programmzeilen weiter heißt es dann vielleicht

name$ = "Johann Karl Fürchtegott"

und die gleiche Variable benötigt plötzlich 23 Bytes — wohin damit? Da es eine gigantische Platzverschwendung wäre, für jede Textvariable von vornherein die maximal möglichen 255 Byte zu reservieren, bleibt nur die Möglichkeit, den String an einer anderen Stelle unterzubringen. Aus diesem Grund kann sich die Speicheradresse des Variableninhalts während des Programmablaufs mehrmals ändern. Damit er trotzdem auffindbar bleibt, legt der Interpreter für jeden String ein drei Bytes umfassendes 'Formular' an, das folgende Informationen enthält:

1) die Länge des Strings (1 Byte)
2) das Low- und Highbyte der aktuellen Stringadresse (2 Bytes)

Dieser sogenannte Stringdeskriptor (Stringbeschreiber) stellt dabei eine Art Wegweiser zum Variableninhalt dar. Zu beachten ist in diesem Zusammenhang, daß bei leeren Strings (zum Beispiel nach name$ = ““) das Längenbyte eine 0 enthält und die Stringadresse Undefiniert ist!

Otto, der Speicherzombie

Doch was geschieht mit Otto, nachdem Johann Karl Fürchtegott seinen Platz eingenommen hat? Traurig, aber wahr: Er fristet fortan ein nutzloses Dasein als Speicherleiche. Die vier Bytes bleiben zwar erhalten, aber es gibt keinen Deskriptor mehr, der auf sie verweist. Macht man exzessiv von Stringoperationen Gebrauch, so wird der Speicher des CPC von HIMEM an abwärts zunehmend mit solchen Zombies bevölkert, da der Interpreter konsequent für jeden geänderten String neuen Speicherplatz mit Beschlag belegt. Das gilt selbst dann, wenn eine Zeichenkette kürzer als ihr Vorgänger ist. Durch ihre hemmungslose Vermehrung werden die lebenden und toten Strings natürlich irgendwann mit dem Speicherbereich für normale Variablen kollidieren, der vom BASIC-Programm-code an aufwärts wächst. In diesem Fall bleibt dem Interpreter nichts anderes übrig, als eine große Aufräumaktion zu starten, die mit 'Garbage Collection' (Müllabfuhr) bezeichnet wird.

Der CPC 464 durchsucht zu diesem Zweck alle existierenden Deskriptoren, um den String mit der höchsten Adresse zu finden und ihn anschließend an das obere Ende des Stringbereichs (unterhalb HIMEM) zu verschieben. Danach wird analog der String mit der zweithöchsten Adresse ermittelt und nach oben verschoben, dann der String mit der dritthöchsten Adresse... bis schließlich alle Zeichenketten, für die ein Deskriptor existiert, wieder optimal komprimiert 'unter der Decke' hängen.

Dieses Verfahren hat jedoch einen gravierenden Nachteil: Da der CPC 464 für jede einzelne Zeichenkette alle Deskriptoren abklappern muß, um die Adressen zu untersuchen, sind bei n Strings insgesamt n Vergleiche notwendig. Ein Array der Form DIM name$(199) erfordert allein schon 200 x 200 = 40000 Schleifendurchläufe, bis endlich Ordnung herrscht. Wenn der CPC 464 gut mit Strings abgefüllt ist, kann ihn die Müllabfuhr während des Programmablaufs durchaus für einige Minuten lahmlegen - bei dieser Gelegenheit hat schon mancher arglose Anwender einen Absturz vermutet und mit einem verzweifelten Reset dem Treiben ein Ende bereitet. Wegen dieser unerfreulichen Erscheinung wurde die Stringverwaltung der Nachfolger CPC 664/6128 verbessert. Diese Rechner stellen jeder Zeichenkette zwei zusätzliche Verwaltungsbytes voran, von denen das erste genau wie der Deskriptor die Länge und das zweite eine Null enthält. Während einer Garbage Collection werden die zwei Bytes benutzt, um dort zeitweise die Adresse des zu dem String gehörigen Deskriptors einzutragen. Diese zusätzliche Verwaltungsinformation erspart eine Menge Suchar-beit und beschleunigt die Müllabfuhr so weit, daß sie sich im Normalfall überhaupt nicht mehr bemerkbar macht. Der Preis ist natürlich ein um zwei Byte höherer Speicherplatzbedarf pro Zeichenkette.

Quäle nie einen Deskriptor zum Scherz...

Das wichtige Fazit dieser Betrachtungen: Wer in Assembler Deskriptoren manipuliert, muß sehr umsichtig vorgehen, um nicht die BASIC-Stringverwaltung zu sabotieren. Auf dem CPC 464 kann man zum Beispiel einen String kürzen, indem man einfach die im Deskriptor angegebene Länge verkleinert. Beim 664/6128 ist das nicht mehr ohne weiteres möglich. Die Länge im Deskriptor muß auf jeden Fall mit dem Inhalt der zwei Verwaltungsbytes übereinstimmen, damit bei der nächsten Garbage Collection kein Chaos ausbricht!

Doch wie kommt man überhaupt in Assembler an die BASIC-Strings heran? Mit dem CALL-Befehl lassen sich bekanntermaßen bis zu 32 Ganzzahlwerte als Parameter übergeben, die der Interpreter in einer Tabelle ablegt, deren Startadresse im IX-Register zu finden ist. Von dieser Möglichkeit wurde in den bisherigen Folgen der Assemblerecke bereits reichlich Gebrauch gemacht; eine Zusammenfassung aller Fakten finden Sie in der Info-Box. Hier soll zunächst noch eine Feinheit erörtert werden, die leicht für Verwirrung sorgt. Es gibt nämlich prinzipiell zwei verschiedene Formen der Parameterübergabe:

1) CALL by Value: Dieser Fall liegt vor, wenn man einen Zahlenwert oder eine Ganzzahl-Variable als Parameter einsetzt, zum Beispiel in der Form CALL &A000,x%. Dem Maschinenprogramm wird dabei der Wert von x% übermittelt , der sich mit den folgenden Befehlen ins BC-Registerpaar einiesen läßt:

10 'LD C,(IX+0) ;Lowbyte x% nach C
20 'LD B,(IX+1) ;Highbyte x% nach B

Es besteht jedoch keine Möglichkeit, den Inhalt der Variablen zu ändern, da ihre Adresse unbekannt bleibt.

2) CALL by Reference: Diese Form der Parameterübergabe liegt vor, wenn man mit Hilfe des Klammeraffen die Variablenadresse übergibt, also zum Beispiel mit CALL &A000,@x%. Um den Wert der Variablen nach BC zu befördern, ist jetzt etwas mehr Aufwand nötig:

10 'LD L,(IX+0) ;Speicheradresse x%
20 'LD H, (IX+1) ;nach HL
30 'LD C,(HL) ;Lowbyte x% nach C
40 'INC HL ; Adresse+1
50 'LD B, (HL) ;Highbyte x% nach B

Der indirekte Zugriff hat jedoch den Vorteil, daß man über die Variablenadresse als Referenz auch etwas in x% hineinschreiben kann. Während ein CALL by Value eine reine Einbahnstraße darstellt, erlaubt ein CALL by Reference die Rückgabe von Ergebnissen an das aufrufende BASIC-Programm!

Adreßverwaltung mit doppeltem Boden

Und nun zurück zu den Strings. Da der CALL-Befehl grundsätzlich 2-Byte-Werte übergibt, bleibt hier nur die Methode 'by Reference', zum Beispiel in der Form CALL &A000,@name$. Dem Maschinenprogramm wird in diesem Fall die Adresse des Stringdeskriptors mitgeteilt. Im Prinzip handelt es sich hier sogar um eine doppelte Referenz: Man erhält die Adresse einer Stelle, an der die Adresse der Zeichenkette zu finden ist. Um das erste Zeichen auszulesen, müßte man also so vorgehen:

10 'LD L, (IX+0) ;Deskriptoradresse
20 'LD H, (IX+1) ;nach HL
30 'INC HL ; Länge überspringen
40 'LD E,(HL) ;Stringadresse
50 'INC HL
60 'LD D, (HL) ;nach DE laden
70 'LD A, (DE) ;1. Zeichen nach A

Besitzer eines 664/6128 sollten sich nicht dadurch täuschen lassen, daß diese Rechner im Unterschied zum 464 auch die direkte Angabe eines Strings bzw. einer Stringvariablen erlauben (zum Beispiel CALL &A000,"Otto"). Hierbei handelt es sich nur um einen gesteigerten Bedienungskomfort: Auf Maschinenebene erscheint nach wie vor die Deskriptoradresse als Parameter!

CALL-Info

Aufrufformat für CALL-Befehl mit n Parametern: CALL adr, p1 , p2 , p3 ..., pn Als Parameter p1..pn können eingesetzt werden:

- Zahlenwerte im Bereich -32768 ... 65535
- Ganzzahl-Variablen (Integer)
- Realvariablen im Bereich -32768... 65535 (werden auf ganze Zahlen gerundet)
- Nur 664/6128: Strings oder Stringvariablen (übergeben wird die Deskriptoradresse)
- Variablenadressen mit @Variablenname (bei Stringvariablen wird die Deskriptoradresse übergeben)

Belegung der Z80-Register beim Einsprung:
A = n (Anzahl Parameter)
E = Lowbyte pn
D = Highbyte pn
IX = Start Parametertabelle

Aufbau der Paranetertabelle:
(IX+0) = Lowbyte pn
(IX+1) = Highbyte pn
(IX+2) = Lowbyte pn-1
(IX+3) = Highbyte pn-1
:
:
(IX+2*n-2) = Lowbyte p1
(IX+2*n-1) = Highbyte p1

Wozu kann man nun den Zugriff auf die BASIC-Zeichenketten effektiv gebrauchen? Manipulationen, die die Länge eines Strings verändern, sind, wie bereits gesagt, nicht unbedingt ratsam. Trotzdem lassen sich auf Anhieb zwei interessante Anwendungen finden, die ein lahmes BASIC-Programm enorm beschleunigen können:

1) Sortieren eines Stringarrays: Die Deskriptoren eines Stringarrays (zum Beispiel DIM name$(400)) liegen glücklicherweise im Speicher ordentlich hintereinander. Übergibt man die Adresse des ersten Deskriptors mit CALL adr,@name$(0), so kann man auch bequem auf alle folgenden Deskriptoren zugreifen. Die häufigste Operation bei Sortier-Algorithmen ist das Vertauschen von Variableninhalten. Das ist in BASIC eine sehr zeitaufwendige Angelegenheit, da massenweise Strings umkopiert und neu angelegt werden müssen. In Assembler kann man sich jedoch darauf beschränken, die Deskriptoren (also nur 3 Bytes) zu vertauschen, was bei erheblich reduziertem Aufwand den gleichen Effekt hat!

2) Suchen von Zeichenketten: Und damit wären wir bei unserem Beispielprogramm angelangt. Es handelt sich um eine Maschinenroutine, die feststellt, ob eine bestimmte Zeichenkette (such$) in einem Stringarray vorhanden ist, und falls ja, an welcher Position. Die Parameterübergabe ist dem Listing zu entnehmen; das Maschinenprogramm erhält dabei folgende Informationen:

  • Die Deskriptoradresse des ersten Strings im Array
  • Die Deskriptoradresse der zu suchenden Zeichenkette
  • Die Anzahl der zu durchsuchenden Arraystrings (by Value!)
  • Die Adresse einer Integer-Variablen (by Reference!), die nach erfolgreicher Suche den Arrayindex enthält. Bei Mißerfolg wird -1 zurückgegeben.

Als besonderes Extra kann der Such-string Fragezeichen als 'Joker' enthalten, die für jedes beliebige Zeichen stehen. Dazu ein kurzes Beispiel: Sie haben eine Adreßverwaltung geschrieben, die bereits 400 Anschriften im Array adress$ verwaltet und einen Herrn namens Meier im korrespondierenden Array na$ ausfindig machen soll. Oder hieß er Mayer? Mit dem folgenden Ansatz findet ihn das Programm auf jeden Fall:

500 sh$="M??er": p%=0
510 CALL &A200, @na$(0), @sh$, 400, @p%
520 if p%=-1 THEN PRINT sh$;" nicht gefunden! “ ELSE PRINT adress$(p%)

Die Analyse des Assembler-Listings dürfte mit den begleitenden Kommentaren keine Probleme aufwerfen. Wer Lust hat, noch etwas zu basteln, kann versuchen, die Routine ein Stück 'fehlertole-ranter' gestalten, so daß sie den Such-string sogar unabhängig von Groß-/Kleinschreibung findet....

Matthias Uphoff/cd , CPCAI

★ PUBLISHER: CPC Amstrad International
★ YEAR: 1989
★ CONFIG: 64K + AMSDOS
★ LANGAGE: ???
★ LICENCE: LISTING
★ AUTHOR: Matthias Uphoff

★ AMSTRAD CPC ★ DOWNLOAD ★

Je participe au site:
» Newfile(s) upload/Envoye de fichier(s)
★ AMSTRAD CPC ★ A voir aussi sur CPCrulez , les sujets suivants pourront vous intéresser...

Lien(s):
» Coding Src's » Fast-Plot (CPC Amstrad International)
» Coding Src's » 3D Cube
» Coding Src's » Yin und Yang mit BOS (CPC Magazin)
» Coding Src's » Hot City Lights
» Coding Src's » Config - Systemidentifikator (CPC Amstrad International)
» Coding Src's » 364 Couleurs

QUE DIT LA LOI FRANÇAISE:

L'alinéa 8 de l'article L122-5 du Code de la propriété intellectuelle explique que « Lorsque l'œuvre a été divulguée, l'auteur ne peut interdire la reproduction d'une œuvre et sa représentation effectuées à des fins de conservation ou destinées à préserver les conditions de sa consultation à des fins de recherche ou détudes privées par des particuliers, dans les locaux de l'établissement et sur des terminaux dédiés par des bibliothèques accessibles au public, par des musées ou par des services d'archives, sous réserve que ceux-ci ne recherchent aucun avantage économique ou commercial ». Pas de problème donc pour nous!

CPCrulez[Content Management System] v8.7-desktop/cache
Page créée en 107 millisecondes et consultée 28 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.