Adressierungsmodi
KI-Hinweis: Dieser Text entsteht mit Unterstützung von KI-Systemen, wird aber vor der Veröffentlichung redaktionell geprüft und verantwortet.
Jeder Operand einer Instruktion trägt einen Modus. Er sagt nicht nur, welche Zelle gemeint ist, sondern auch, wie man dorthin kommt — und bei vier der acht Modi verändert die Berechnung dabei den Speicher.
Genau diese vier sind die häufigste Fehlerquelle der Sprache.
Übersicht
| Zeichen | Name | Adresse ist … |
|---|---|---|
# |
unmittelbar | keine Adresse — der Wert selbst |
$ |
direkt | die Zelle so viele Schritte entfernt |
@ |
B-indirekt | über das B-Feld der Zielzelle weitergeleitet |
* |
A-indirekt | über das A-Feld der Zielzelle weitergeleitet |
< |
B-indirekt, vorher −1 | wie @, aber das B-Feld wird vorher verringert |
> |
B-indirekt, nachher +1 | wie @, aber das B-Feld wird danach erhöht |
{ |
A-indirekt, vorher −1 | wie *, aber das A-Feld wird vorher verringert |
} |
A-indirekt, nachher +1 | wie *, aber das A-Feld wird danach erhöht |
Alle Werte sind relativ zur ausführenden Zelle und werden modulo der
Coregröße gerechnet. $1 heißt „eine Zelle weiter", nicht „Adresse 1". Es gibt
keine absoluten Adressen in Redcode.
Die Modi im Einzelnen
# — unmittelbar
ADD.AB #4, $ptr
Der Operand ist kein Zeiger, sondern eine Zahl. #4 bedeutet die Vier selbst.
Kleine Feinheit, die selten stört, aber gelegentlich verwirrt: Auch ein
unmittelbarer Operand hat formal eine Adresse — nämlich die der ausführenden
Zelle. Relevant wird das nur bei .I-Operationen und in Randfällen.
$ — direkt
MOV.I $0, $1
Der Wert ist der Abstand zur Zielzelle. $0 ist die Zelle selbst, $1 die
nächste, $-1 die vorherige.
Das $ darf weggelassen werden — MOV.I 0, 1 bedeutet dasselbe. In dieser
Referenz steht es trotzdem überall, weil expliziter Code leichter zu lesen ist.
@ und * — indirekt
MOV.I $bomb, @bomb
Zwei Schritte statt einem. Erst wird die Zelle bestimmt, auf die der Operand
zeigt; dann wird aus deren Feld ein weiterer Sprung ausgeführt. @ liest
das B-Feld, * das A-Feld.
Das ist der Mechanismus, mit dem ein kleiner Warrior einen großen Speicher
bearbeitet: Er schreibt einen Zeiger fort und greift indirekt darüber zu, statt
selbst zu wachsen. Der Dwarf tut das mit @bomb — die Bombe ist gleichzeitig
Munition und Zeiger.
< > { } — indirekt mit Nebenwirkung
MOV.I }src, >dst
Diese vier arbeiten wie @ und *, verändern aber im Vorbeigehen das Feld, über
das sie springen:
<und{verringern vor dem Zugriff um eins>und}erhöhen nach dem Zugriff um eins
Damit bekommt man einen Schleifenzähler geschenkt, ohne eine eigene Instruktion
dafür zu verbrauchen. Ein Replikator, der sich Zelle für Zelle kopiert, braucht
so nur eine MOV-Zeile statt einer MOV- plus einer ADD-Zeile — und in Core
War entscheiden einzelne Zyklen.
Die Falle: Nebenwirkungen treten immer ein
Der Punkt, an dem die meisten Warrior-Fehler entstehen:
Die Nebenwirkung passiert beim Auflösen der Adresse, nicht beim Ausführen des Befehls. Sie tritt also auch dann ein, wenn der Operand für das Ergebnis gar keine Rolle spielt.
JMZ.B $loop, <counter
JMZ springt nur, wenn die geprüfte Zelle null ist. Aber das < hat den Zähler
in jedem Fall schon um eins verringert — ob gesprungen wird oder nicht.
Dasselbe gilt für übersprungene Instruktionen? Nein — eine Instruktion, die
wegen SEQ übersprungen wird, wird gar nicht ausgeführt, also passiert auch
nichts. Die Falle betrifft nur Operanden innerhalb einer ausgeführten
Instruktion, die für deren Ergebnis nicht gebraucht werden.
Die Reihenfolge: A vor B
Wenn beide Operanden Nebenwirkungen haben und dieselbe Zelle betreffen, wird die Reihenfolge wichtig:
Der A-Operand wird zuerst aufgelöst, der B-Operand danach.
Das heißt: Der B-Operand sieht die Änderung, die der A-Operand verursacht hat — umgekehrt aber nicht.
Das ist keine Randnotiz aus dem Standard, sondern beobachtbares Verhalten. Der Beleg lässt sich in acht Zeilen bauen:
ORG go
p DAT.F #0, #5
DAT.F #0, #0
DAT.F #0, #0
DAT.F #0, #0
MOV.I $0, $1 ; ein Imp — läuft an jeder Position
target DAT.F #0, #0 ; tödlich, solange nicht überschrieben
go MOV.I <p, @p
JMP.A $target
Der Zeiger in p steht auf 5, also auf target. Die entscheidende Zeile ist
MOV.I <p, @p:
- Wird A zuerst aufgelöst, verringert
<pden Zeiger auf 4. Danach liest@pdenselben, nun verringerten Zeiger — beide Operanden zeigen auf dieselbe Zelle. Der Imp wird auf sich selbst kopiert,targetbleibt einDAT, und der Sprung dorthin tötet den Prozess. - Würde B zuerst aufgelöst, zeigte
@pnoch auf 5, also auftarget. Der Imp würde dorthin kopiert, der Sprung landete auf einem Imp, und der Warrior liefe weiter.
Gemessen über 200 Runden: 200 Niederlagen, keine einzige überlebte Runde. Der Warrior stirbt zuverlässig — A wird zuerst aufgelöst.
Übliche Muster
Ein paar Kombinationen, die dir in fast jedem Warrior begegnen:
| Muster | Zweck |
|---|---|
MOV.I $bomb, @ptr |
Bombe an eine fortgeschriebene Adresse werfen |
MOV.I }src, >dst |
Block kopieren; beide Zeiger laufen automatisch mit |
ADD.AB #step, $ptr |
Zeiger um eine feste Schrittweite versetzen |
DJN.F $loop, <ptr |
Schleife zählen und Zeiger in einem Zug versetzen |
SEQ.I $ptr, $ptr+dist |
zwei Stellen vergleichen — die Grundform des Scannens |
Verwandte Seiten
- Befehlssatz — alle 16 Opcodes
- Modifier — welche Felder eine Instruktion anfasst
- Redcode verstehen — die Einführung im Lernpfad