The 16 registers and their halves

The 16 registers and their halves

x86-64 gives ordinary integer code sixteen general-purpose integer register families. They are small, named workspaces inside the processor. An instruction can use one to hold an integer value or an address; the name does not permanently assign a meaning to the value.

The processor also has other kinds of registers. This lesson is only about these sixteen general-purpose integer registers and the different names for parts of each one.

The sixteen families

The first eight families have names inherited from older versions of x86. The remaining eight use a numbered pattern. All sixteen are equally 64 bits wide when named with their r... form.

64-bit32-bit16-bitlow 8-bit
raxeaxaxal
rbxebxbxbl
rcxecxcxcl
rdxedxdxdl
rsiesisisil
rdiedididil
rbpebpbpbpl
rspespspspl
r8r8dr8wr8b
r9r9dr9wr9b
r10r10dr10wr10b
r11r11dr11wr11b
r12r12dr12wr12b
r13r13dr13wr13b
r14r14dr14wr14b
r15r15dr15wr15b

The d, w, and b endings in the numbered names mean 32-bit, 16-bit, and 8-bit. For example, r14, r14d, r14w, and r14b are four ways to name parts of one register family.

One family, several overlapping names

The names in one table row do not describe separate storage locations. They select overlapping low parts of the same 64-bit register. Least-significant means the part that represents the smallest place values of a binary number. It is at the right-hand end when a hexadecimal value is written in the usual order.

For rax, the bytes can be pictured like this. b0 is the least-significant byte.

 rax  [ b7 ][ b6 ][ b5 ][ b4 ][ b3 ][ b2 ][ b1 ][ b0 ]
 eax                    [ b3 ][ b2 ][ b1 ][ b0 ]
 ax                                  [ b1 ][ b0 ]
 ah                                  [ b1 ]
 al                                        [ b0 ]

So if rax contains 0x1122334455667788, then eax reads 0x55667788, ax reads 0x7788, ah reads 0x77, and al reads 0x88. Reading a narrower name takes only that low portion; it does not change the register.

Here are copies of several portions. Immediately before the template at the end, the destination registers have the values listed below.

registervalue before the template
rbx0xAAAAAAAAAAAAAA88
rcx0xBBBBBBBBBBBBBB77
r100xCCCCCCCCCCCC7788
r110x0000000055667788
r120x1122334455667788

The first four families also have the old high-byte names ah, bh, ch, and dh. Each selects the second-lowest byte, as ah does above. Prefer the ordinary low-byte names when possible: high-byte names cannot appear in the same instruction as an r8r15 name.

What a narrow write leaves behind

Writing an 8-bit or 16-bit register name replaces only that low portion. The upper bits stay as they were. A write to any 32-bit general-purpose destination is different: it writes the low 32 bits and clears the upper 32 bits to zero.

Before the template, r8 is 0xFFFFFFFFFFFFFF00, r9 is 0xFFFFFFFFFFFF0000, and r10 is 0x0000000000000000.

This rule is especially useful when reading code. mov edx, 5 does more than put 5 in the low half of rdx: it makes the complete 64-bit rdx equal to 0x0000000000000005.

Your turn

r8 holds 0xCAFEBABEDEADBEEF. Copy its least-significant byte to bl, its least-significant two bytes to cx, and its least-significant four bytes to edx. The initial destination values are chosen so that the preserved and cleared upper bits are visible.

Show solution

Use the numbered register names directly in this one. Copy the low byte of r9 to r13b, the low two bytes of r10 to r14w, and the low four bytes of r11 to r15d.

Show solution