Addressing modes
AI notice: This text is created with the support of AI systems; it is reviewed editorially and taken responsibility for before publication.
Every operand of an instruction carries a mode. It says not only which cell is meant but also how to get there — and for four of the eight modes, the computation alters memory along the way.
Those four are the most common source of errors in the language.
Overview
| Symbol | Name | The address is … |
|---|---|---|
# |
immediate | no address — the value itself |
$ |
direct | the cell that many steps away |
@ |
B-indirect | forwarded through the target cell's B-field |
* |
A-indirect | forwarded through the target cell's A-field |
< |
B-indirect, pre-decrement | like @, but the B-field is decremented first |
> |
B-indirect, post-increment | like @, but the B-field is incremented afterwards |
{ |
A-indirect, pre-decrement | like *, but the A-field is decremented first |
} |
A-indirect, post-increment | like *, but the A-field is incremented afterwards |
All values are relative to the executing cell and computed modulo the core
size. $1 means "one cell along", not "address 1". There are no absolute
addresses in Redcode.
The modes in detail
# — immediate
ADD.AB #4, $ptr
The operand is not a pointer but a number. #4 means the four itself.
A subtlety that rarely matters but occasionally confuses: an immediate operand
formally has an address too — namely that of the executing cell. This only
becomes relevant for .I operations and in edge cases.
$ — direct
MOV.I $0, $1
The value is the distance to the target cell. $0 is the cell itself, $1 the
next one, $-1 the previous one.
The $ may be omitted — MOV.I 0, 1 means the same thing. This reference
writes it everywhere anyway, because explicit code is easier to read.
@ and * — indirect
MOV.I $bomb, @bomb
Two steps instead of one. First the cell the operand points at is determined;
then a further jump is taken from that cell's field. @ reads the B-field,
* the A-field.
This is the mechanism by which a small warrior works on a large memory: it
advances a pointer and accesses memory indirectly rather than growing itself.
The dwarf does this with @bomb — the bomb is simultaneously ammunition and
pointer.
< > { } — indirect with side effect
MOV.I }src, >dst
These four work like @ and * but alter the field they jump through along the
way:
<and{decrement before the access>and}increment after the access
That gives you a loop counter for free, without spending an instruction on it. A
replicator copying itself cell by cell therefore needs only a MOV line instead
of a MOV plus an ADD — and in Core War, individual cycles decide.
The trap: side effects always happen
The point where most warrior bugs originate:
The side effect happens when the address is resolved, not when the instruction executes. It therefore occurs even when the operand plays no part in the result.
JMZ.B $loop, <counter
JMZ only jumps if the tested cell is zero. But the < has already decremented
the counter in either case — whether the jump is taken or not.
Does the same apply to skipped instructions? No — an instruction skipped because
of a SEQ is not executed at all, so nothing happens. The trap only concerns
operands within an executed instruction that are not needed for its result.
The order: A before B
When both operands have side effects and touch the same cell, the order becomes important:
The A-operand is resolved first, the B-operand afterwards.
That means the B-operand sees the change caused by the A-operand — but not the other way around.
This is not a footnote from the standard but observable behaviour. The proof fits in eight lines:
ORG go
p DAT.F #0, #5
DAT.F #0, #0
DAT.F #0, #0
DAT.F #0, #0
MOV.I $0, $1 ; an imp — runs at any position
target DAT.F #0, #0 ; lethal unless overwritten
go MOV.I <p, @p
JMP.A $target
The pointer in p holds 5, that is, it points at target. The decisive line is
MOV.I <p, @p:
- If A is resolved first,
<pdecrements the pointer to 4.@pthen reads the same, now decremented pointer — both operands point at the same cell. The imp is copied onto itself,targetstays aDAT, and jumping there kills the process. - If B were resolved first,
@pwould still point at 5, that is, attarget. The imp would be copied there, the jump would land on an imp, and the warrior would keep running.
Measured over 200 rounds: 200 losses, not a single surviving round. The warrior dies reliably — A is resolved first.
Common patterns
A few combinations you will meet in almost every warrior:
| Pattern | Purpose |
|---|---|
MOV.I $bomb, @ptr |
throw a bomb at an advancing address |
MOV.I }src, >dst |
copy a block; both pointers advance automatically |
ADD.AB #step, $ptr |
move a pointer by a fixed step size |
DJN.F $loop, <ptr |
count a loop and move a pointer in one go |
SEQ.I $ptr, $ptr+dist |
compare two locations — the basic form of scanning |
Related pages
- Instruction set — all 16 opcodes
- Modifiers — which fields an instruction touches
- Understanding Redcode — the introduction in the learning path