lea: The Address Calculator
How lea computes an address without touching memory, and why compilers use it for fast arithmetic.
The Instruction That Computes, Not Loads
lea is the instruction that confuses people the longest, and the instruction that, once it clicks, makes you feel like you can really read assembly. It looks like a memory access but it never touches memory. It is written like a mov from a bracketed operand but it does something subtly different and far more useful.
The name stands for Load Effective Address. Here is the whole idea in one sentence: lea takes a memory operand, computes the address that operand describes, and puts that address into a register, without ever reading from memory.
Contrast it with a normal load. Given [rdi + rsi*4]:
mov eax, [rdi+rsi*4]computes the addressrdi + rsi*4, then goes to memory at that address and loads the value there intoeax.lea rax, [rdi+rsi*4]computes the same addressrdi + rsi*4and stops there, putting the address itself intorax. No memory is accessed.
So lea keeps the address-computing half of a memory operand and throws away the memory-access half. The previous lesson showed how rich those address recipes are (displacement[base + index*scale]). lea hands you that whole calculation as a general-purpose arithmetic engine.
The Honest Use: Taking an Address
The literal purpose of lea is computing the address of something, which is exactly what C++'s & operator and array-element addresses need. Consider:
int* addressOf(int* base, int i)
{
return &base[i];
}
At -O2:
addressOf(int*, int):
movsx rsi, esi
lea rax, [rdi+rsi*4]
ret
Compare this directly with elementAt from the previous lesson, which returned base[i]:
mov eax, DWORD PTR [rdi+rsi*4] ; elementAt: returns the value
lea rax, [rdi+rsi*4] ; addressOf: returns the address
Identical address computation; the only difference is mov versus lea. elementAt dereferences and returns the int; addressOf returns the address itself. That is &base[i] in C++. This is the most natural way to understand lea: it is what & compiles to whenever an address needs to be calculated. You also see it constantly in -O0 code as lea rax, -4[rbp], which is just "compute the address of a local variable."
The Clever Use: Fast Arithmetic
Here is where lea earns its reputation. Because the address recipe base + index*scale + displacement is just arithmetic, and because lea performs that arithmetic and writes the result to a register, the compiler can hijack lea to do ordinary integer math, no memory involved.
Look again at the add function from Chapter 1:
int add(int a, int b)
{
return a + b;
}
add(int, int):
lea eax, [rdi+rsi]
ret
There is no array, no pointer, nothing to do with memory. a is in rdi, b in rsi, and lea eax, [rdi+rsi] computes rdi + rsi and writes the sum to eax. The compiler used the address calculator as an adder. Why not add? Because lea can read two source registers and write a third destination in one instruction, whereas add eax, esi would overwrite one of its inputs. lea lets the compiler add a + b into eax while leaving both rdi and rsi untouched, all in a single instruction.
It gets more impressive with a richer expression:
int combine(int a, int b)
{
return a + b * 4 + 7;
}
combine(int, int):
lea eax, 7[rdi+rsi*4]
ret
One instruction. The operand 7[rdi+rsi*4] computes rdi + rsi*4 + 7, which is a + b*4 + 7 exactly. A multiply by 4, an add, and another add, three arithmetic operations, folded into a single lea. This is why you see lea so often in optimized code where you might have expected a sequence of imul and add: when a computation fits the base + index*scale + displacement shape, one lea does the lot.
How to Tell Which Use You Are Looking At
Since lea serves both purposes, how do you know whether a particular lea is computing an address or just doing math? Context, mostly:
- If the destination register is later dereferenced (used inside brackets in a subsequent
mov), theleawas computing an address. - If the destination is used as a plain value (returned, added to, compared), the
leawas doing arithmetic.
In practice you often do not even need to decide. lea rax, [rdi+rsi*4] computes rdi + rsi*4 either way; whether you call the result "an address" or "a number" is just interpretation. The instruction is the same. That dual nature is precisely what makes lea so handy to the compiler and, at first, so puzzling to the reader.
Two Things lea Does Not Do
A couple of clarifications that save confusion:
leanever reads or writes memory. Despite the brackets, the only thing it writes is the destination register, and the only things it reads are the registers inside the brackets. It cannot fault on a bad address because it never visits one.leadoes not set flags. Ordinary arithmetic likeaddandsubupdate the CPU's status flags (covered in Chapter 3).lealeaves them alone. That is occasionally another reason a compiler preferslea: it can compute a value without disturbing flags that a nearby comparison still needs.
The Reading Habit
When you hit a lea, do two quick things. First, read the bracket as the arithmetic expression base + index*scale + displacement, the same way you learned to read any memory operand. Second, decide from context whether the result is being used as an address or as a number. With those two moves, lea goes from the most mysterious instruction in the listing to one of the most informative: it often packs an entire small computation into a single line.
Key Takeaways
lea(Load Effective Address) computes the address described by a memory operand and stores that address in a register, without accessing memory.- The difference from
mov:mov eax, [x]loads the value at addressx;lea eax, [x]loads the addressxitself. - Its honest use is taking addresses, which is how C++
&and array-element addresses compile (lea rax, [rdi+rsi*4]for&base[i]). - Its clever use is fast arithmetic:
lea eax, [rdi+rsi]adds two registers, andlea eax, 7[rdi+rsi*4]computesa + b*4 + 7in one instruction. - Compilers favour
leabecause it reads multiple registers, writes a separate destination, and does not modify the flags. - To read a
lea: expand the bracket intobase + index*scale + displacement, then use context to tell whether the result is an address or a value.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
lea: The Address Calculator - Quiz
Test your understanding of the lesson.
Practice Exercises
Fold an Expression into a Single lea
Finish the combine function so it returns a + b * 8 + 3. The program prints combine(10, 2), combine(5, 4), and combine(1, 1). This expression has exactly the base + index*scale + displacement shape, so the compiler can compute all of it with one lea. Once your output is correct, read the assembly panel and find the lea whose operand contains a base register plus an index register scaled by 8.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!