Registers are small storage locations inside the CPU that instructions use as working space while a program runs.
Learning Question
If C programs are written with variables, why do assembly instructions keep using registers?
C code lets the programmer name values with variables:
int result = left + right;Assembly often shows values moving through registers:
movl -20(%rbp), %edx
movl -24(%rbp), %eax
addl %edx, %eaxThis can be confusing because it is tempting to treat registers as if they were simply another spelling of C variables.
They are not.
The first mental model is:
Registers are the CPU’s temporary working locations. C variables are source-level names. The compiler decides how values associated with C variables move through registers and memory.
What a Register Is
A register is a small storage location inside the CPU.
Instructions can read values from registers, write values into registers, and use register-held values for arithmetic, comparisons, address calculations, and control-flow decisions.
Registers are much closer to the CPU than ordinary process memory.
That closeness is why many machine instructions are shaped around registers.
At a simplified level, a CPU instruction often does one of these things:
- put a value into a register
- copy a value from one register to another
- read memory into a register
- write a register value back to memory
- perform arithmetic using register-held values
- use a register-held value as an address or part of an address
This does not mean every instruction uses only registers.
It means registers are central working locations in the instruction-level model.
Registers Are Not C Variables
A C variable is a source-level concept.
It has a name, a type, a scope, and C language rules.
A register is a machine-level storage location.
It has a hardware-defined name and can hold bit patterns while instructions execute.
These are different kinds of things:
| Concept | Level | Role |
|---|---|---|
| C variable | Source level | A named object or value used by the C program |
| Register | Instruction level | A CPU storage location used by generated instructions |
The compiler may use a register to hold the value associated with a C variable.
But that does not make the register the variable.
A single C variable’s value may move through several registers and memory locations over time.
A single register may hold values associated with different C variables at different moments.
An optimized program may not preserve a separate runtime location for a C variable at all.
The better question is not:
Which register is this variable?
The better question is:
At this instruction, where is the value associated with this source-level concept?
Registers in the Addition Example
Consider the same function:
int add(int left, int right)
{
int result = left + right;
return result;
}One possible GCC -O0 x86-64 sequence includes:
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl -20(%rbp), %edx
movl -24(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eaxThis sequence uses several registers:
| Register | Role in This Example |
|---|---|
%edi | holds one incoming integer argument value |
%esi | holds another incoming integer argument value |
%edx | temporarily holds the value loaded from left’s stack slot |
%eax | temporarily holds the value loaded from right, then the addition result, then the return value |
Do not treat this table as a universal rule.
It describes this generated example.
The durable lesson is that the compiler uses registers as working locations while implementing the C function’s behavior.
The Same Register Can Change Roles
In the example, %eax appears several times:
movl -24(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eaxAt first, %eax holds the value loaded from right.
After the addl instruction, %eax holds the sum of left and right.
After the result is stored to -4(%rbp), %eax is later loaded again with the return value.
This shows why a register should be understood by its role at a specific moment.
The register does not permanently mean right.
It does not permanently mean result.
It is working storage whose meaning comes from the current instruction sequence.
Registers Hold Values, Not Source Meaning
A register holds bits.
The C program gives those bits meaning through types and operations.
For example, the C source says:
int result = left + right;The source-level type int tells the compiler which kind of addition is required and how many bits of value are relevant.
The assembly example uses 32-bit integer instructions such as:
movl
addlThe l suffix in this AT&T-style assembly indicates a 32-bit operation in this example.
You do not need to memorize every suffix yet.
The important point is that the instruction selects a machine-level operation over a certain width of data.
The register itself does not remember that the value came from a C variable named left or right.
That source-level meaning exists in the compiler’s translation and in the programmer’s reasoning, not inside the register as a name.
Why Register Use Is Visible in Assembly
C hides register use because C is not written as direct CPU instruction control.
When writing C, the programmer usually says:
result = left + right;not:
load this value into one CPU register
load that value into another CPU register
add them
store the resultAssembly reveals that lower-level shape.
This is useful because many execution questions depend on where a value is right now:
- Is the value still in memory?
- Was it loaded into a register?
- Which instruction overwrote the register?
- Did the result get stored back to memory?
- Is the return value in the register expected by the calling convention?
Those questions cannot be answered by reading C variable names alone.
Assembly exposes the register-level movement.
Register Names Are Architecture-Specific
The names %eax, %edx, %edi, and %esi are x86-64 register names in AT&T-style assembly syntax.
Other architectures use different names.
For example, ARM, RISC-V, and other architectures have their own register sets and assembly syntax.
Even on x86-64, different assemblers may print syntax differently.
This collection uses x86-64/GCC examples when concrete assembly helps, but the first concept is not tied to those exact names.
The transferable idea is:
Machine instructions need concrete working locations, and registers are the CPU’s primary working locations.
Registers and Memory Work Together
Registers and memory are not competing explanations.
They work together during execution.
In the addition example:
movl -20(%rbp), %edx
movl -24(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)The values are read from memory-like stack locations into registers.
The addition happens in registers.
The result is written back to a stack location.
This is a common pattern:
memory -> register -> operation -> register -> memoryLater, optimized code may keep values in registers longer and avoid some memory writes.
That does not change the role of registers.
It makes their role even more important.
What Optimization Can Change
The -O0 example is intentionally easy to read.
With optimization enabled, the compiler may use registers differently.
It might avoid storing parameters to stack locations.
It might compute the result directly in the return register.
It might remove the local variable result as a separate runtime location.
For example, the compiler still has to preserve the source-level behavior of returning left + right, but it does not have to preserve a visible stack slot named result.
This reinforces the boundary:
C requires behavior. It does not require a fixed register or memory location for every source-level name.
What This Chapter Does Not Explain Yet
This chapter explains registers as CPU working space.
It does not yet fully explain:
- stack frames
- why stack offsets such as
-20(%rbp)appear - calling conventions
- why arguments arrive in particular registers
- why return values use particular registers
- condition flags
- instruction encoding
- register allocation inside the compiler
Those topics matter, but they should be approached after the basic role of registers is clear.
For now, the key boundary is:
A C variable is a source-level name. A register is a machine-level working location that may temporarily hold the value associated with that name.
Core Mental Model
Keep these boundaries separate:
- Registers are small CPU storage locations used by instructions.
- C variables are source-level names with type, scope, and language meaning.
- A variable’s value may be held in memory, in a register, moved between them, or optimized away as a separate location.
- A register can hold different values at different moments in the same function.
- Assembly is useful because it shows when generated instructions read, write, and overwrite registers.
When reading assembly, ask:
What value does this register hold at this point, and what instruction last wrote it?
That question is more useful than trying to permanently match one C variable to one register.
Final Summary
Registers are the CPU’s working space.
C code names values with variables, but generated instructions operate through concrete locations such as registers and memory.
Assembly helps reveal how values move through registers while the machine implements the behavior described by the C source.