What does the lea (Load Effective Address) instruction do, and why do compilers love it?
lea computes an address expression and stores the address itself — it does not read memory, and unlike arithmetic it leaves the condition flags untouched.
* lea computes an address (no memory access, no flags set) while mov dereferences it; lea doubles as cheap multiply-add. *
lea borrows the syntax of a memory operand but, instead of loading from that address, just hands you the address. That makes it a sneaky general-purpose arithmetic unit:
leaq (%rdi,%rsi,4), %rax # rax = rdi + rsi*4 (an address calc)
movq (%rdi,%rsi,4), %rax # rax = MEM[rdi + rsi*4] (an actual load)
Three things compilers use it for:
- Array element addresses —
&A[i]:leaq (%rdi,%rsi,4), %rax. - Cheap multiply/add without touching flags —
rax = rdi*3:leaq (%rdi,%rdi,2), %rax. The scale factor (1/2/4/8) plus the base lets one instruction doa + b*k. - Fused address arithmetic —
rax = rdi + rsi*4 + 7:leaq 7(%rdi,%rsi,4), %rax.
Key property: because lea never accesses memory and never sets flags, the compiler can slip it between a cmp and a jcc without disturbing the comparison — a mov+add+shl sequence couldn't do that.
Tip: Seeing lea whose result is later used as a number (not dereferenced) is a tell that the compiler is doing arithmetic, not address computation.
Go deeper:
LEA — x86 instruction reference — the LEA instruction reference.
Addressing mode (Wikipedia) — the addressing mode LEA evaluates.