Addressing Modes
Addressing Modes
ARM64 supports several ways to compute memory addresses. Understanding these modes is key to writing efficient code -- the right addressing mode can save instructions when working with arrays and data structures.
Like the navigational deflector on the Enterprise, addressing modes give you different ways to plot a course to your data -- each optimized for a different flight plan.
Summary of Modes
| Mode | Syntax | Effect |
|---|---|---|
| Offset | [X1, #8] | Access X1+8, X1 unchanged |
| Pre-index | [X1, #8]! | X1 += 8, then access X1 |
| Post-index | [X1], #8 | Access X1, then X1 += 8 |
| Register offset | [X1, X2] | Access X1+X2, both unchanged |
Offset Addressing
The base register is not modified. The offset is added to compute the address:
LDR X0, [X1, #16] // Load from X1+16, X1 unchanged
STR X0, [X1, #8] // Store to X1+8, X1 unchanged
This is the most common mode -- ideal for accessing struct fields or array elements at known offsets.
Pre-Index Addressing
The offset is added to the base register before the memory access, and the base register is updated:
LDR X0, [X1, #8]! // X1 = X1+8, then load from X1
STR X0, [X1, #-16]! // X1 = X1-16, then store to X1
The ! suffix means "write back" -- the base register is permanently updated. This is commonly used for stack operations (allocating stack space before storing).
Post-Index Addressing
The memory access uses the base register as-is, then the offset is added:
LDR X0, [X1], #8 // Load from X1, then X1 = X1+8
STR X0, [X1], #16 // Store to X1, then X1 = X1+16
This is perfect for walking through arrays: load the current element and advance the pointer in one instruction.
Register Offset Addressing
You can use a register as the offset, which is useful for indexing arrays with a variable:
LDRB W0, [X1, X2] // Load byte from X1 + X2
Walking Through an Array with a Loop
Post-index is especially natural for sequential access. Compare these two approaches:
// Without post-index (2 instructions per element):
LDRB W0, [X1] // Load byte
ADD X1, X1, #1 // Advance pointer
// With post-index (1 instruction per element):
LDRB W0, [X1], #1 // Load byte AND advance pointer
The real power of post-index shows when combined with a loop. Instead of repeating LDRB/ADD for each element, you can process any number of elements with a fixed set of instructions:
MOV X2, #4 // counter = array length
MOV X5, #0 // sum = 0
loop:
LDRB W1, [X0], #1 // Load byte AND advance pointer
ADD X5, X5, X1 // Accumulate into sum
SUBS X2, X2, #1 // counter-- (sets flags)
B.NE loop // Repeat if counter != 0
We are sneaking a peek at two instructions from later lessons:
SUBSworks likeSUBbut also sets condition flags (theSsuffix means "set flags"). When the result is zero, it sets the Zero flag.B.NE("Branch if Not Equal") jumps to the label if the Zero flag is not set -- i.e., the counter has not reached zero.
Together they form a counted loop: decrement, check for zero, repeat. This pattern scales to arrays of any size -- just change the counter.
Tip: Use post-index for forward iteration and pre-index for stack operations. Offset addressing is best when you know the exact position at compile time.
Your Task
An array of four bytes is defined in the data section: 3, 7, 2, 8. Using post-index addressing in a loop, load each byte, add them all together, convert the sum (20) to ASCII, and print it followed by a newline.