Intuition What this page is
The parent note (the topic note ) told you the five recipes. Here we hammer every corner case until no scenario can surprise you: pointers that chase pointers, index registers that go to zero or backwards, addresses that wrap, and an exam-style trap. Each example first asks you to forecast , then walks the resolution step by step, then verifies .
One notation reminder, built from zero so nothing is assumed:
M [ x ] means "the value stored in the memory cell whose address is the number x ." Read it as "memory-at-x ."
R [ i ] means "the value currently held in CPU register number i ."
EA = Effective Address = the final address number the CPU actually reads the operand from.
Every worked example below is tagged with the cell of this matrix it covers. The goal: hit all of them.
Cell
Case class
What makes it tricky
C1
Immediate — a plain constant
Zero memory access; operand is the bits
C2
Register — operand already inside CPU
No bus trip; contrast with memory modes
C3
Direct — literal address
Baseline "1 read" case
C4
Indirect — pointer chase (2 reads)
Distinguish M [ A ] from M [ M [ A ]]
C5
Indexed — forward array walk
EA = A + R [ i ] , positive index
C6
Indexed — zero index (degenerate)
R [ i ] = 0 ⇒ EA = A , same cell as direct
C7
Indexed — negative index (backward)
Signed index, walking down an array
C8
Limiting / wrap — address near memory top
Address arithmetic modulo memory size
C9
Word problem — real array loop
Choosing the right mode
C10
Exam twist — mixed indirect + indexed
Order of operations trap
The shared machine state for the examples (unless a problem overrides it):
The figure above is the mental model for every example: an address is just a house number on a street , and each mode is a different clue for finding the right house.
MOV R1, #42
Forecast: how many memory reads happen to get the operand, and what lands in R1?
Decode the mode. The # marks immediate. Why this step? The mode decides whether we even touch memory — everything downstream depends on it.
The operand is the field itself. The bits 42 were already pulled in during instruction fetch . Why? Immediate stores the value inside the instruction word , so no separate operand fetch is needed.
R1 ← 42.
Verify: operand = 42 , 0 operand memory accesses. Sanity: had we treated 42 as an address, we'd have read M [ 42 ] — a bug. The # guards against exactly that.
ADD R1, R6 with R1 = 10, R6 = 200
Forecast: result in R1, and number of memory reads.
Both operands are register contents. Why? Register mode says the field names a register, not an address.
Fetch R 1 = 10 and R 6 = 200 from the register file — internal wires, no memory bus .
R1 ← 10 + 200 = 210.
Verify: R 1 = 210 , 0 memory accesses. Compare with C3 next: same value 200 shows up, but C3 pays a memory read to get it while here it was free in a register — this is why registers exist .
LOAD R1, [100]
Forecast: EA and the value in R1.
EA = 100 directly. Why? Direct mode: the bracketed number is the address, no computation.
Read M [ 100 ] = 200 . Why one read? One read resolves nothing extra — the address was literal, so the single read is the operand fetch.
R1 ← 200.
Verify: EA = 100 , operand = 200 , 1 access. Look at the blue clue in the figure below — the arrow points straight from the instruction to the house.
LOAD R1, [[100]]
Forecast: before reading — write down both addresses touched, then the operand.
First read (resolve the pointer): M [ 100 ] = 200 . Why? The double brackets mean 100 is a pointer to the address , not the address of the data. This read gives us the real address.
EA = 200. Why? The value we just fetched is the effective address.
Second read (fetch operand): M [ 200 ] = 350 .
R1 ← 350.
Verify: EA = 200 , operand = 350 , 2 accesses. The orange path in the figure shows the two hops: instruction → house 100 → (note says "go to 200") → house 200. Contrast C3: direct stops after hop one.
Common mistake Stopping after one read in indirect
If you wrote R1 ← 200 you did direct on a pointer . Indirect always has one more M [ ⋅ ] than direct. See pointers — this is exactly dereferencing *p.
LOAD R1, [100 + R2] with R2 = 4
Forecast: EA and value.
Add the index register to the base: EA = 100 + R 2 = 100 + 4 = 104 . Why add R 2 and not read M [ 100 ] ? Indexed does arithmetic , not an extra memory read — that is its whole speed advantage over indirect.
Read M [ 104 ] = 17 .
R1 ← 17.
Verify: EA = 104 , operand = 17 , 1 access. With element size 4, offset 4 means "element index 4/4 = 1 " — the second element of the array based at 100. See stride .
LOAD R1, [100 + R3] with R3 = 0
Forecast: which other mode does this collapse into?
EA = 100 + R 3 = 100 + 0 = 100 . Why care? A zero index is the first element of an array — a real, common case (loop counter starts at 0).
Read M [ 100 ] = 200 .
R1 ← 200.
Verify: EA = 100 , operand = 200 , 1 access. Notice: this produced the same EA as direct C3 . That's not a coincidence — indexed with index 0 is direct addressing. The degenerate case is a bridge between the two modes, not a bug.
LOAD R1, [204 + R5] with R5 = -8
Forecast: does EA go up or down? Then compute it.
EA = 204 + R 5 = 204 + ( − 8 ) = 196 . Why can the index be negative? The index register holds a signed number; a negative index walks down the array (e.g. iterating from the last element toward the first).
Read M [ 196 ] = 42 .
R1 ← 42.
Verify: EA = 196 , operand = 42 , 1 access. Sanity on sign: 196 < 204 , so we moved backward by 8 bytes = 2 elements. The figure below shows the index arrow pointing left for a negative index and right for a positive one.
LOAD R1, [250 + R6_low] with an 8-bit address space (0..255) and index = 10
Forecast: 250 + 10 = 260 — but there are only 256 cells (0..255). What happens?
Compute raw EA = 250 + 10 = 260 . Why is this a problem? 260 does not name a real cell; the address bus is only 8 bits wide.
Wrap modulo memory size: EA = 260 mod 256 = 4 . Why modulo? An 8-bit adder physically drops the carry-out bit, which is exactly arithmetic mod 2 8 = 256 . The hardware cannot represent 260.
Read M [ 4 ] (whatever lives there). The point is which cell , not its value.
Verify: 260 mod 256 = 4 . This is the limiting/degenerate boundary case: address arithmetic is modular . Overflowing the top of memory silently wraps to the bottom — a classic source of bugs. Any real EA is always taken mod (memory size).
Worked example You must add up 3 integers stored at addresses 100, 104, 108. Which mode, and trace it.
Values: M [ 100 ] = 200 , M [ 104 ] = 17 , M [ 108 ] = 64 . Element size 4.
Forecast: which mode lets you reuse one instruction inside a loop?
Choose indexed. Why not direct? Direct hard-codes one address per instruction — you'd need three different LOAD instructions. Indexed reuses one instruction and just bumps the index.
Set base A = 100 , index R 2 = 0 . Loop 3 times:
Iter 0: EA = 100 + 0 = 100 , read 200 . Sum = 200 . Then R 2 ← R 2 + 4 = 4 .
Iter 1: EA = 100 + 4 = 104 , read 17 . Sum = 200 + 17 = 217 . Then R 2 ← 8 .
Iter 2: EA = 100 + 8 = 108 , read 64 . Sum = 217 + 64 = 281 . Then R 2 ← 12 .
Why increment by 4, not 1? Each element is 4 bytes wide, so the next element sits 4 addresses further — that step size is the stride (Arrays and Stride ).
Verify: total = 200 + 17 + 64 = 281 , 3 memory reads (one per element, all indexed → 1 each). Same instruction ran 3 times; only R 2 changed. This is the loop advantage in the datapath .
LOAD R1, [[300 + R3]] with R3 = 0 — indexed then indirect.
Recall M [ 300 ] = 100 , M [ 100 ] = 200 .
Forecast: how many memory reads, and the final value? (Trap: people stop too early.)
Indexed part first: EA 1 = 300 + R 3 = 300 + 0 = 300 . Why first? The index arithmetic builds the address that the indirect will then dereference.
First memory read (indirect resolves pointer): M [ 300 ] = 100 . So the real EA is 100 .
Second memory read (fetch operand): M [ 100 ] = 200 .
R1 ← 200.
Verify: final operand = 200 , 2 memory reads. The trap is stopping at step 2 with R1 ← 100 (that's the pointer, not the data) or forgetting the index add in step 1. Order: compute address → dereference → dereference .
Recall Quick self-check on the matrix
Which cells produce the same EA , and why? ::: C3 (direct [100]) and C6 (indexed [100+R3], R3=0) both give EA=100 — indexed with a zero index degenerates into direct.
Why does C8 wrap 260 down to 4? ::: The 8-bit adder drops the carry, so EA = 260 mod 256 = 4.
In C10, why 2 reads not 1? ::: Indirect always adds one extra memory read to fetch the pointer before fetching the operand; the index add is arithmetic (free), so total = 2.
Negative index in C7 — does EA rise or fall? ::: Falls: 204 + (−8) = 196, walking backward through the array.
Mnemonic The corner-case triad
Whenever you see indexed, check three edges: zero index (→ direct), negative index (→ walk back), overflow index (→ wrap mod memory). Miss one and an exam or a real bug will find it.