Skip to content
Registers and Arithmetic
step 1/5

Reading — step 1 of 5

Learn

~4 min readFirst Steps

Registers and Arithmetic

Registers are the CPU's scratch space: 16 general-purpose slots, each 64 bits wide, built into the processor itself. Memory is far away (in CPU terms); registers are right there. Nearly every instruction reads or writes one, so fluency with registers is fluency with assembly.

One register, four names

The 64-bit general-purpose registers are rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, and r8r15. Each can also be addressed as a narrower slice of itself:

  • rax (64-bit) → eax (low 32) → ax (low 16) → al (low 8)
  • r8 (64-bit) → r8d (low 32) → r8w (low 16) → r8b (low 8)

These are views of the same storage, not separate registers — and that's where the classic width trap lives:

  • Writing the 32-bit view zeroes the upper 32 bits: after mov eax, 5, all of rax is exactly 5. That's why compilers prefer mov eax, 5 over mov rax, 5 — same result, smaller instruction.
  • Writing the 16-bit or 8-bit view leaves the upper bits unchanged: after mov al, '7', bits 8–63 of rax still hold whatever was there before. Use all of rax as a number afterwards and that leftover garbage comes along for the ride.

Arithmetic: destination first

NASM is Intel syntax: the destination comes first, and it doubles as the left-hand input:

mov rax, 7      ; rax = 7
add rax, 3      ; rax = rax + 3   -> 10
sub rax, 4      ; rax = rax - 4   -> 6
imul rax, 2     ; rax = rax * 2   -> 12  (signed multiply)
inc rax         ; rax += 1        -> 13
dec rax         ; rax -= 1        -> 12
xor rax, rax    ; rax = 0

xor rax, rax is the idiomatic zero: anything XOR itself is 0, it encodes shorter than mov rax, 0, and CPUs recognize it as a fresh start (no dependency on the register's old value). If you have seen AT&T syntax (addl $3, %eax — source first), note that operand order here is the mirror image; reading it backwards is a rite-of-passage bug.

The stack gives you quick save/restore:

push rax        ; rsp -= 8, then [rsp] = rax
pop rbx         ; rbx = [rsp], then rsp += 8

Touching memory: brackets and sizes

Square brackets mean "the memory at this address":

mov rax, [counter]     ; load 8 bytes from counter into rax
mov [counter], rbx     ; store rbx (8 bytes) at counter
mov byte [digit], '7'  ; store exactly ONE byte

That byte keyword is not decoration. In mov [digit], '7', neither operand pins down a size — digit is just an address, '7' is just a number — so NASM refuses to guess and errors with operation size not specified. When one operand is a register, the register's width implies the size; for immediate-to-memory stores you must write byte, word, dword, or qword yourself.

Characters are numbers wearing costumes

Assembly has no character type. The terminal receives bytes and renders them: byte 0x37 (decimal 55) is drawn as 7, byte 0x39 as 9, byte 10 as a line break. NASM's '7' is just a convenient way to write 0x37.

So these are two different programs:

mov byte [digit], '9'   ; stores 0x39 — the visible character 9
mov byte [digit], 9     ; stores 0x09 — a horizontal TAB

Drop the quotes and you print an invisible control character. The output looks almost right in a terminal — and is byte-for-byte wrong.

Your exercise

The starter's data line is digit db 0, 10: two adjacent bytes — a placeholder, then a newline. The code stores '7' into the first byte and then writes 2 bytes starting at digit, so the digit and the newline go out in one write. Change the stored character so the program prints 9 followed by a newline — exactly the bytes 0x39 0x0A. The mistake the grader catches: writing mov byte [digit], 9 without quotes, which prints a tab instead of 9 — the code "looks right" and the diff still fails. Keep the quotes: '9'. And leave rdx at 2, or you'll drop the newline the test expects.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…