★ CODING ★ LISTINGS ★ Die Assemblerecke|CPC Amstrad International '89/3) ★ |
Heute: The BASIC String Orchestra & The Assembler Punks (CPC Amstrad International) | Coding Listings |
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) 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 |
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 Belegung der Z80-Register beim Einsprung: Aufbau der Paranetertabelle: |
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:
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:
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
|
|