Referenz
2026-07-18 · Lesezeit ~4 min

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 <p den Zeiger auf 4. Danach liest @p denselben, nun verringerten Zeiger — beide Operanden zeigen auf dieselbe Zelle. Der Imp wird auf sich selbst kopiert, target bleibt ein DAT, und der Sprung dorthin tötet den Prozess.
  • Würde B zuerst aufgelöst, zeigte @p noch auf 5, also auf target. 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


← Core-War-Übersicht