The Dreadful Flying Glove points to B$
Var-Pointer
Var-Pointer
Ein Var-Pointer tut genau das, was sein Name sagt: Er zeigt auf eine Variable. Genauer: auf den Ort, wo sich der Wert einer Variablen im Speicher befindet.

Im C64 ist dies die Speicherstelle 71/72 ($47/$48). Wann immer eine Variable verwendet wird, kann man hier ablesen, wo im Speicher sie ist.

Problem bei der Anwendung: Die gesuchte Variable muss im Programm-Code logisch die letzte vor der Abfrage sein. Man kann das Ergebnis also nur anzeigen, aber nicht so einfach einer anderen Variablen zuweisen.

Lösung:

Der Einsatz von Funktionen wie Var-Pointer setzt Kenntnisse über die C64-interne Verarbeitung von Variablen voraus und ist daher nur für spezielle Anwendungen empfehlenswert. Eine solche Anwendung ist der Einbau von (kurzen) Maschinensprache-Routinen in ein Basic-Programm, "verkleidet" als Strings. Solche Routinen müssen relokatibel sein (frei verschieblich, also ohne den Gebrauch von absoluten Adressen im Code) und dürfen eine Länge von 255 Bytes nicht überschreiten, weil ein String eben nur maximal 255 Zeichen lang sein kann.

Ein Beispiel für solche Routinen wäre eine verlustfreie Directory-Anzeige innerhalb eines Basic-Programms. Man weist dem String per READ/DATA die Codes der Routine zu, merkt sich die Adresse des Strings und ruft den Code bei Bedarf mit einem SYS auf die gemerkte Adresse auf. Var-Pointer würde sich also die Adresse der String-Variablen holen. Was Var-Pointer zurückliefert, zeigt direkt hinter die zwei Zeichen des Variablennamens (bei Arrays auf die Position der angesprochenen Zelle). In einer String-Variablen steht dort die Länge des Strings, die uns aber weniger interessiert. Wir brauchen die zwei Bytes dahinter, diese beiden Bytes stellen die Adresse des eigentlichen String-Inhalts dar, den wir uns für einen späteren Aufruf von Maschinenroutinen im String in einer beliebigen numerischen Variable merken.

Hinweis: Seit TSB-Version 2.31113 gilt eine andere Aufrufadresse! (Statt $8096 jetzt $8b65)Vorgehensweise (der String soll MC$ heißen und den Maschinen-Code bereits enthalten, und die Variable A muss bereits vorher angelegt worden sein):

sys $8b65 mc$: a=peek(780)+256*peek(782): a=d!peek(a+1)

In der Variablen A steht danach die Adresse des ersten Zeichens im String (das erste A zeigt auf die Länge des Strings, die wir nicht brauchen, daher "+1"), sinnvollerweise die Einsprungsadresse für eine wie oben angedachte Maschinenroutine. Sie kann nun mit SYS A aufgerufen werden.

Eine Variante, die ohne ein anfängliches SYS auskommt, ist diese Konstruktion:

a=0*len(mc$)+d!peek(71):a=d!peek(a+1)

Wir verzichten in der ersten Variante auf den systemeigenen Var-Pointer in 71/72 und haben zum Auffinden der Variablen die spezielle Variablen-Suchroutine des C64 genutzt (in TSB an $8b65), die ihre Ergebnisse in den CPU-Registern zurückliefert (Akku und Y). Beide sind nach einem SYS an den Speicherstellen 780 und 782 abrufbar (die leider nicht aufeinanderfolgen). Sie zeigen auf die gleiche Stelle wie 71/72 (tatsächlich erzeugt die dabei aufgerufene Kernal-Routine $b08b die Speicherstellen 71/72).


Hinweis nebenbei: Der TSB-Befehl AT() zum "abfallfreien" Austausch zweier Strings funktioniert nach genau diesem Prinzip. Bei den beiden Strings werden einfach nur die Zeiger auf deren Inhalt ausgetauscht. Es entsteht keinerlei Verzögerung durch Umkopiererei und auch keinerlei String-Müll, gut für Sortieraufgaben.


Ein anderes Anwendungsbeispiel findet sich im Programm "show godots reu" auf der "TSB Demo Disk". Hier geht es ebenfalls um einen String, diesmal jedoch wird er als Zielort eines Datentransports aus einer angeschlossenen REU verwendet (könnte aber auch ein beliebiger anderer Datenträger sein). Das genannte Programm zeigt das REU-Directory von GoDot an (wenn man vorher GoDot auf seinem System hat laufen lassen).

Jeder Directory-Eintrag ist dort 19 Bytes lang, hat also eine feste Länge, weshalb der Zielstring (D$) natürlich auf eine Länge von 19 Bytes initialisiert wird (Z. 90). Ebenfalls in Zeile 90 wird Var-Pointer aufgerufen (mit dem klangvollen Befehl "stringadresse holen"). Hier die entsprechenden Passagen:

80 cls: cset1: c$=chr$(0)
90 d$="...................": ls=len(d$):
   stringadresse holen

100 memcont 0: memrestore 1: memlen ls
110 mempos 0,0: memor sa: memload: dd$=mid$(d$,5,10)
115 if place(c$,dd$) then stringreset: goto 295
120 print "Directory of Drive 12: " dd$: print

1000 proc stringadresse holen
1010 sys $8b65 d$: p=peek(780)+256*peek(782)
1015 sa=d!peek(p+1)
1020 end proc






Die Zeilen 100 und 110 bereiten den Transport aus der REU vor und führen ihn durch (mit MEMLOAD) und Z. 115 prüft, ob in der REU überhaupt ein GoDot-Directory angelegt wurde. Wenn nicht, wird die Einleseroutine übersprungen, ansonsten geht es los mit der Überschrift (Z. 120) und dem Namen des virtuellen GoDot-Laufwerks 12 ("RM.Handler" in DD$). Die Einleseroutine sieht dann so aus:

130 nx=d!peek(sa+1): bk=peek(sa+3)

210 for l=1 to 119
230 mempos l*19,0: memor sa: memload
240 p=l: if peek(sa)=$c0 then l=119: goto 290
245 if asc(mid$(d$,4))=0 then stringreset: goto 290
250 use" ### ",l;: print mid$(d$,4) at(lin,25)"$" $$peek(sa)and15;
255 print $$d!peek(sa+1)
260 if lin=24 then keyget x$: cls: print
290 next











Mithilfe von Var-Pointer: Mit jedem MEMLOAD wird der String D$ mit neuem Inhalt gefüllt, ohne irgendeinen Stringmüll zu verursachen.

Es gibt 119 mögliche Directory-Einträge mit je 19 Bytes Länge (Z. 210). Die ersten drei Bytes enthalten die Adresse des in der REU eingelagerten Moduls. Zuerst die Bank, dann in zwei Bytes den Offset in dieser Bank. Ab Byte 4 folgt der Name des Moduls. Alles wird ausgegeben (Z. 230 und 250/255).

Die Zeilen 240 und 245 überprüfen, ob vielleicht das Ende des Directorys erreicht ist (wenn ein Eintrag mit $C0 beginnt) oder ob es defekt ist und die eingelesenen Daten Probleme bei der Ausgabe machen würden (wenn der Modulname fehlt und stattdessen eine $00 erkannt wird). Die Zählschleife wird dann abgebrochen. Die Zeile 130 ist dafür zuständig, am Ende des Programms den nächsten freien Platz in der REU anzuzeigen, wobei die Variable P (die in Z. 240 den aktuellen Zählerstand von der Variablen L übernimmt) als Flag dafür dient, ob überhaupt etwas eingelesen wurde (Z. 295 bis 305):

295 if p=1 then do
296 print: print "No REU attached
    or GoDot didn't use one."
297 else
300 print:print "Next entry at $"
    $$bk; $$nx; "," 120-p "entries left"
305 done
999 end
1050 proc stringreset
1060 for i=0 to ls-1: poke sa+i,$c0: next: p=1: l=119
1070 end proc

Fehlt noch "stringreset", das in den Zeilen 115 und 245 (wenn Fehler auftreten) aufgerufen wird: Da der String D$ im Basic-Code liegt, muss hier mit POKEs ein für LIST ungefährlicher Zustand sichergestellt werden.