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 der Var-Pointer der Zeiger an Speicherstelle 71/72 ($47/$48). Wann immer eine Variable verwendet wird, kann man hier ablesen, wo im Speicher diese Variable angelegt wurde.

Problem bei der Anwendung von 71/72: Die gesuchte Variable muss die letzte Variable vor der Suche sein. Man kann also nicht eine Variable suchen und das Ergebnis im gleichen Zug einer anderen Variablen zuweisen. Es gibt zwei Wege, das Problem zu umgehen.

Eine Variante, die nur für Strings gilt, ist diese Konstruktion:

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

Hier wird die Position der Stringvariablen MC$ gesucht. Durch die Verwendung der LEN-Funktion wird diese Variable vom System verwendet und ihre Adresse daher in 71/72 abgelegt. Das Ergebnis von LEN wird aber durch die Multiplikation mit 0 sofort verworfen. Stattdessen lesen wir mit D!PEEK den Ort der Variablen aus, addieren ihren Wert auf und haben so genau das, was wir erreichen wollten: den Ort der Variablen MC$.

Ein Var-Pointer zeigt dabei (immer) auf das erste Byte nach dem Namen der Variablen im Speicher. Dort findet man bei Strings dessen Länge. Erst in den beiden folgenden Bytes steht die endgültige Speicheradresse des Stringinhalts. Die Formel oben müsste für das Auslesen der Speicheradresse so erweitert werden: ad=d!peek(a+1). Über den Wert von AD haben wir dann direkten Zugriff auf den Inhalt des Strings. Auch die einzelnen Zellen eines String-Arrays sind hiermit erreichbar.

Eine weitere Variante gilt für Variablen jeder Art:

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

Auch hierbei steht in der Variablen A danach die Adresse der Länge des Strings. Mit der gleichen AD-Formel wie eben kommen wir aber auch hier auf dessen Inhalt (mit ad=d!peek(a+1)).

Bei allen anderen Variablentypen zeigt der Var-Pointer (diesmal eben 780/782) direkt auf den Inhalt der Variablen. Bei Integervariablen folgt dort der 16-Bit-Integerwert (auslesen mit w=d!peek(a) bzw. manipulieren mit d!poke a), bei Fließkommavariablen folgen die fünf Bytes des Floatwertes, die dann genauso ausgelesen oder manipuliert werden könnten. Auch diese Methode des Var-Pointers funktioniert bei Arrays, 780/782 zeigt bei Aufruf einer bestimmten Array-Zelle genau auf den Speicherort dieser Zelle.

Eine clevere Anwendung für Var-Pointer ist der Einbau von Maschinensprache-Routinen in ein Basic-Programm, "verkleidet" als Strings. Solche Routinen müssen dazu frei verschieblich, also ohne den Gebrauch von absoluten Adressen im Code (relokatibel) sein und dürfen natürlich eine Länge von 255 Bytes nicht überschreiten, weil ein String eben nur maximal 255 Zeichen lang sein kann. Im TSB-Programm ByPixx kann man sich dafür eine Beispielroutine anschauen.

Allgemeine Vorgehensweise für diese Methode: 1. einen neuen String anlegen, der das Maschinenprogramm aufnehmen soll, 2. mit Var-Pointer die Adresse des Inhalts dieses Strings herausfinden, 3. das Maschinenprogramm per POKEs (und damit ohne Stringmüll zu verursachen!) in den String injizieren, 4. bei Bedarf mit SYS ad aufrufen (Rückgabewerte über die Adressen 780 bis 782).

Ein anderes Anwendungsbeispiel findet sich im hier unten aufgeführten Programm "godot reu directory" (das noch auf älteren Versionen der TSB-Disk zu finden ist). Hier geht es ebenfalls um einen String, diesmal jedoch wird er als Zielort eines Datentransports aus einer angeschlossenen REU verwendet (Datenquelle 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 GoDot-Directory-Eintrag ist in der REU 19 Bytes lang, hat also eine feste Länge, weshalb der Zielstring (D$) natürlich auf diese Länge von 19 Bytes initialisiert wird (Z. 90, Vorgehensweise Punkt 1). Ebenfalls in Zeile 90 wird Var-Pointer aufgerufen (mit dem klangvollen Befehl "stringadresse holen", Zeile 1000, Vorgehensweise Punkt 2). 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, Vorgehensweise Punkt 3) und Z. 115 prüft, ob in der REU überhaupt ein GoDot-Directory angelegt wurde (dann dürfte sich kein Zeichen mit dem Wert 0 im geholten String befinden). Wenn nicht, wird die Einleseroutine übersprungen, ansonsten geht es los mit der Überschrift (Z. 120) und dem Namen des virtuellen GoDot-Laufwerks (Laufwerk 12, Name 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 place(c$,mid$(d$,4)) 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











Es gibt 119 mögliche Directory-Einträge mit je 19 Bytes Länge (Z. 210). Die ersten drei Bytes jedes Eintrags enthalten die Adresse des in der REU eingelagerten GoDot-Modifiers. 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, das entspricht der Vorgehensweise Punkt 4).

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 in den gelesenen Daten 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 Laufvariablen 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 memclr sa,ls,$c0: 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$ mitten im Basic-Programm-Code liegt, muss hier mit MEMCLR ein für ein mögliches LIST ungefährlicher Endzustand des Basic-Programms sichergestellt werden.