Effective addresses

Effective addresses

For the ordinary bracketed memory operands used in this course in 64-bit mode, x86 can calculate an address from this pattern:

[base + index * scale + displacement]

The calculated number is the effective address: the address of the first byte that the instruction accesses.

  • base is the value in a 64-bit general-purpose register.
  • index is the value in a 64-bit general-purpose register, which may be the same register as the base. rsp cannot be the index.
  • scale is 1, 2, 4, or 8.
  • displacement is a fixed integer such as 8 or -16.

The base, index, and displacement are independently optional, but the expression must contain at least one component. The scale belongs to the index, so it is omitted when there is no index.

Here are several valid shapes:

    mov r8, [rbx]                 ; base
    mov r8, [rbx + 8]             ; base + displacement
    lea r13, [rsi*8]              ; index * scale, with no base
    mov r8, [rbx + rsi*8]         ; base + index * scale
    mov r8, [rbx + rsi*8 + 16]    ; all four parts

In each mov, the processor calculates the address and then reads a qword from memory because r8 is a 64-bit register.

Indexing a qword array

In this lesson, array indices start at zero. Index 0 selects the first element, index 1 selects the second, and index 2 selects the third. This is called zero-based indexing.

Suppose a four-qword array begins at address 0x402000:

indexbyte offseteffective addressvalue there
00 * 8 = 00x40200010
11 * 8 = 80x40200820
22 * 8 = 160x40201030
33 * 8 = 240x40201840

Each qword occupies eight bytes, so the byte offset of index i is i * 8. If rbx contains the array's starting address and rsi contains the index, [rbx + rsi*8] selects that qword.

After the four loads, r8 = 10, r9 = 20, r10 = 30, and r12 = 40. Those values remain in the same registers when the program exits.

The four scale values are an x86 encoding rule. They also match the common strides of byte, word, dword, and qword arrays:

element widthbyte stridescale
byte11
word22
dword44
qword88

The processor does not know the declared type of an array and does not choose the scale for you. A qword load with scale 4 still reads eight bytes; it simply starts at the wrong address for qword indexing. When an element's byte stride is not 1, 2, 4, or 8, calculate the needed byte offset in a register before using it in a memory operand.

lea calculates the number

Compare these two instructions when rbx is the array address and rsi is 2:

    mov r12, [rbx + rsi*8]
    lea r13, [rbx + rsi*8]

mov calculates rbx + rsi * 8, accesses memory at that address, and puts the qword stored there in r12. For the array above, that value is 30.

lea calculates the same number and puts the number itself in r13. It does not access memory. If rbx is 0x402000, then r13 becomes 0x402010, the address of the qword containing 30.

The name lea means load effective address. Its bracketed operand describes a calculation, while its first operand is the one destination register:

    lea r13, [rbx + rsi*8]      ; r13 = address of array[index]

The calculated number does not have to be used as an address. The same allowed scales make lea useful for a few compact integer calculations:

At exit, r12 = 8, r13 = 56, r14 = 35, and r15 = 59. In the third calculation, rsi fills both the base and index roles, so the result is five times rsi.

Your turn

grid contains four qwords. Read two groups of indexed elements, using rbx as the base address and each index with the qword scale.

  • For the first group, r12, r13, and r14 contain indices 3, 0, and 2. Put the selected qwords in r8, r9, and r10.
  • For the second group, r15, rsi, and rdx contain indices 1, 3, and 0. Put the selected qwords in r12, r13, and r14. The first group has already used their original index values, so these registers can now hold results.
Show solution

Now calculate 9 * x + 2 for three inputs. The inputs are r8 = 0, r9 = 1, and r10 = 5. Put the corresponding results 2, 11, and 47 in r12, r13, and r14. Try using lea for each calculation: nine times a value can be written as the value plus eight times the value.

Show solution

Label addresses and default rel

A label such as arr names an address chosen when the program is assembled and linked. In 64-bit mode, default rel tells NASM to use a RIP-relative reference for a bare label when that instruction form allows it. Writing rel makes the choice explicit:

    mov r8, [rel arr]           ; read the qword at arr
    lea rbx, [rel arr]          ; calculate the address of arr

rip is the instruction pointer, the register that identifies the instruction being executed. In this addressing form, it supplies the address immediately after the current instruction. The two instructions above encode the signed distance from that address to arr, and the processor adds the distance to rip while the instruction runs. The distance must fit in a signed 32-bit displacement. This form lets the reference remain valid if the code and nearby data move together.

RIP-relative addressing cannot include an index register in the same memory operand. For indexed arrays, first put the label's address in a register, then use that register as the base:

    lea rbx, [rel arr]
    mov r8, [rbx + rsi*8]

This is the practical pattern used throughout the lesson.