roadmap
week 1 · day 6

mov & Addressing Modes

The five ways to name a value — and why brackets change everything

Easy 35 min 180 xp
After this you can
  • 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.

Note
Syntax note for this course: we write Intel syntax (what NASM uses), where the destination is first: 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.

#ModeExampleMeans
1Immediatemov rax, 42a literal constant, baked into the instruction
2Registermov rax, rbxthe value currently in a register
3Register-indirectmov rax, [rbx]the value in memory AT the address in rbx
4Base + displacementmov rax, [rbx + 8]memory at (rbx + 8)
5Base + index*scale + dispmov 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.

Key idea
Brackets are dereference. 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:

effective address = base + index*scale + displacement
   [ 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.

the five modes, side by side (Intel / NASM)asm
1 mov rax, 42 ; (1) immediate: rax = 42
2 mov rax, rbx ; (2) register: rax = rbx
3 mov rax, [rbx] ; (3) indirect: rax = *(qword*)rbx
4 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:

RegisterSizeBytesExample
raxqword8mov rax, [rbx] → copies 8 bytes
eaxdword4mov eax, [rbx] → copies 4 bytes
axword2mov ax, [rbx] → copies 2 bytes
albyte1mov 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:

sizing an ambiguous storeasm
1 mov [rbx], 1 ; ERROR: 1 byte? 4 bytes? 8? the assembler can't tell
2 mov byte [rbx], 1 ; store a single 0x01 byte
3 mov qword [rbx], 1 ; store 8 bytes: 01 00 00 00 00 00 00 00

The one thing mov can't do

Try to copy memory to memory in a single instruction:

the forbidden moveasm
1 mov [rbx], [rcx] ; ERROR: two memory operands, not allowed
2 ; 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.

Predict first
Before revealing: rbx = 0x1000, rcx = 3. What effective address does mov rax, [rbx + rcx*4 + 8] read from? Compute it by hand.
Key idea
The three ideas to carry out of here: (1) brackets mean "the memory at this address" — that's dereference; (2) [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.
finished reading?
Your task, you write the code

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.

deliverable: sum.asm
build & run
$ nasm -f elf64 sum.asm -o sum.o
$ ld sum.o -o sum
$ ./sum ; echo $? # should print your array's sum
self-review before running
  • 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
stretchChange the array to 8-byte values (qwords) and make it still work — what single number do you change, and why? Then, in gdb, `x/4xw &array` to see your elements in memory and confirm the addresses differ by exactly 4.

Self-check

01After `mov rax, rbx`, what is true of rbx?
02What does `[rbx]` mean, versus `rbx`?
03In `[rbx + rcx*4]` used to read an array, why is the scale 4?
04Why is `mov [rbx], [rcx]` illegal?
0/4 correct · 0/4 checked