mov & Addressing Modes
The five ways to name a value — and why brackets change everything
- › Explain why mov copies rather than moves, and what an 'operand' really is
- › Name a value five ways: immediate, register, and three flavours of memory
- › Compute an effective address from base + index*scale + displacement, by hand
- › Say why the CPU must know the operand size, and why mem-to-mem in one mov is impossible
The instruction you'll type ten thousand times
Almost every program, underneath, is mostly one instruction repeated: mov. If you understand mov completely — not the syntax, the *idea* — a huge fraction of assembly stops being cryptic. So we're going to take it apart to the bottom.
First, a lie in the name. mov does not *move*. It copies. After mov rax, rbx, the value is
in *both* registers; rbx is untouched. Hold that: mov reads a source and writes a copy to a
destination, and the source is left exactly as it was.
mov dest, src. So mov rax, rbx means "copy rbx into rax." (You'll also meet AT&T
syntax, which reverses the order — same machine, different clothes. We'll flag it when it matters.)What is an "operand," really?
An instruction like mov dest, src has two operands — the two things it acts on. The entire
richness of mov is in the *ways you're allowed to name each operand*. There are exactly five, and
they're worth learning as a set, because every other instruction (add, cmp, lea…) uses the same
five. Learn them once here; reuse them forever.
| # | Mode | Example | Means |
|---|---|---|---|
| 1 | Immediate | mov rax, 42 | a literal constant, baked into the instruction |
| 2 | Register | mov rax, rbx | the value currently in a register |
| 3 | Register-indirect | mov rax, [rbx] | the value in memory AT the address in rbx |
| 4 | Base + displacement | mov rax, [rbx + 8] | memory at (rbx + 8) |
| 5 | Base + index*scale + disp | mov rax, [rbx + rcx*4 + 8] | memory at (rbx + rcx*4 + 8) |
Modes 1 and 2 name a value directly — a constant, or a register's contents. Modes 3, 4, and 5 are all the *same idea at growing power*: the brackets mean "go to memory at this address." That is the single most important thing in this lesson.
rbx is the *value in rbx*. [rbx] is *the value in memory at the
address held by rbx*. This is exactly what *p will mean in C (day 16) — a pointer dereference —
except here you can see it's just "use this number as an address and read the bytes there." Registers
without brackets = data; registers with brackets = an address to follow.The general form, and why scale is only 1, 2, 4, or 8
Mode 5 is the workhorse that walks arrays and structs. Its effective address — the actual memory address the CPU computes — is:
[ base + index * scale + disp ]
| | | |
a register a reg 1,2,4,8 a constant
(start) (which (element (fixed
element) size) offset)Why can scale only be 1, 2, 4, or 8? Because it exists to multiply an *array index* by an
*element size*, and the element sizes that matter are 1 byte (char), 2 (short), 4 (int/float), and 8
(long/double/pointer). array[i] in a language becomes base_of_array + i * sizeof(element) —
and that is *literally* [base + index*scale]. You are seeing a[i] compiled, three weeks before
we write it in C.
1 mov rax, 42 ; (1) immediate: rax = 422 mov rax, rbx ; (2) register: rax = rbx3 mov rax, [rbx] ; (3) indirect: rax = *(qword*)rbx4 mov rax, [rbx + 8] ; (4) base+disp: rax = *(qword*)(rbx + 8)5 mov rax, [rbx + rcx*4] ; (5) full form: rax = *(qword*)(rbx + rcx*4)Read that last line like a sentence
mov rax, [rbx + rcx*4]: take the address in rbx (the base of an int array), add rcx (the
index) times 4 (the size of an int), and copy the 8 bytes *at that address* into rax. If rbx is
the array and rcx is i, this is rax = array[i] for an array of 4-byte ints. Change the 4 to an
8 and it's an array of longs. The scale *is* the element size.
You must say how many bytes
A subtle but crucial point: memory has no type. [rbx] is just an address — the CPU needs to know
how many bytes to copy. In modes that involve a register, the *register name* answers this:
| Register | Size | Bytes | Example |
|---|---|---|---|
| rax | qword | 8 | mov rax, [rbx] → copies 8 bytes |
| eax | dword | 4 | mov eax, [rbx] → copies 4 bytes |
| ax | word | 2 | mov ax, [rbx] → copies 2 bytes |
| al | byte | 1 | mov al, [rbx] → copies 1 byte |
rax, eax, ax, al are not four registers — they're four *views* of the same one, at 8, 4,
2, and 1 bytes. (This is the "meaning is imposed on the same bits" idea from day 1, in hardware.)
When *neither* operand is a sized register — e.g. writing an immediate to memory — you must add a
size specifier yourself, or the assembler can't know:
1 mov [rbx], 1 ; ERROR: 1 byte? 4 bytes? 8? the assembler can't tell2 mov byte [rbx], 1 ; store a single 0x01 byte3 mov qword [rbx], 1 ; store 8 bytes: 01 00 00 00 00 00 00 00The one thing mov can't do
Try to copy memory to memory in a single instruction:
1 mov [rbx], [rcx] ; ERROR: two memory operands, not allowed2 ; you must go through a register:3 mov rax, [rcx] ; load (memory → register)4 mov [rbx], rax ; store (register → memory)Why? A single x86 instruction may reference at most one memory operand — a hardware limit rooted in how instructions are encoded and executed. So the fundamental rhythm of assembly is load, compute, store: pull values from memory into registers, do work in registers, write results back. Registers are the workbench; memory is the warehouse. Almost everything you'll read is that dance.
mov rax, [rbx + rcx*4 + 8]
read from? Compute it by hand.[base + index*scale + disp] is how a[i] is computed, with scale = element
size; (3) the CPU must know the byte count, and can touch at most one memory operand per
instruction — hence load, compute, store.Sum an array in assembly
In sum.asm, put four 4-byte integers in memory (an array), then use a loop and the [base + index*scale] addressing mode to add them into a register, and exit with the sum as the process's exit code so you can read it with `echo $?`. Everything you need — mov modes, scale = element size, sizing, load/compute/store — is in this lesson; the loop mechanics you saw on day 5's structure (a label and a jump) are the only reach, so keep the array to 4 elements and it stays within what you know.
$ nasm -f elf64 sum.asm -o sum.o$ ld sum.o -o sum$ ./sum ; echo $? # should print your array's sum- › You used [base + index*scale] to read each element (scale = 4, the int size)
- › You can explain why the scale is 4 and not 1
- › You loaded each element into a register before adding (no mem-to-mem)
- › echo $? shows the correct sum for your four numbers