Skip to content

LEA

LEA 指令的全称是 Load Effective Address(加载有效地址)。

它是汇编语言中极其常用、且有点“不务正业”的指令。虽然它的本意是计算内存地址,但程序员经常用它来做快速算术运算。

为了让你彻底理解,我们从它的基本用法、与 MOV 的区别以及它的“副业”三个方面来讲解。


一、 基本用法:计算地址

LEA 的语法格式是:lea destination, [source]

它的作用是:计算 [source] 里的地址公式,把结果存入 destination,但不去访问内存。

示例:

lea rsi, [rbx + 8]
* 动作:取出 rbx 寄存器的值,加上 8。 * 结果:把这个计算结果直接存入 rsi。 * 重点:它没有去内存里看 [rbx + 8] 这个地方存了什么,它只是算出了这个地址本身。


二、 LEA vs MOV:核心区别

这是初学者最容易混淆的地方。请看下面这两行代码:

; 假设 rbx = 0x1000,且内存地址 0x1008 处存的值是 0x55

mov rax, [rbx + 8]   ; 第一种:MOV
lea rdx, [rbx + 8]   ; 第二种:LEA
  1. MOV 指令(取包裹):

    • 它计算出 0x1000 + 8 = 0x1008。
    • 它去内存 0x1008 的位置看了一眼,发现里面存的是 0x55。
    • 结果:rax = 0x55。
  2. LEA 指令(写地址):

    • 它计算出 0x1000 + 8 = 0x1008。
    • 它直接把 0x1008 这个数字存进了寄存器。
    • 结果:rdx = 0x1008。

类比: * MOV 就像是你按照地址去取快递,最后你手里拿到的是包裹里的东西。 * LEA 就像是你把地址抄在纸上,最后你手里拿到的只是那个地址数字。


三、 LEA 的“副业”:快速算术运算

由于 x86 的寻址模式支持 [基址 + 变址 * 比例因子 + 偏移量],LEA 成了做简单加法和乘法的神器。

规则: lea dest, [reg1 + reg2 * scale + constant] * scale 只能是 1, 2, 4, 8。

场景 1:实现 x = y + z

lea rax, [rbx + rcx]  ; rax = rbx + rcx
这比 add 指令更灵活,因为 add 会修改操作数本身(比如 add rbx, rcx 会改变 rbx),而 lea 可以保持原值不变,把结果存到新寄存器里。

场景 2:实现 x = y * 5 + 10

利用比例因子和加法:

lea rax, [rbx + rbx * 4 + 10]
* rbx + rbx * 4 相当于 rbx * 5。 * 整条指令一步到位完成了乘法和加法。 * 优点:速度极快,比执行专门的 MUL 指令效率高得多。


四、 为什么要用 LEA?

  1. 取指针:在 C 语言里,当你写 p = &arr[i]; 时,编译器后端通常会生成一条 LEA 指令,算出数组元素的地址给指针。
  2. 不影响标志位:LEA 是纯粹的地址计算,它不会改变标志寄存器(RFLAGS)。
    • 如果你想在不破坏 ZF(零标志)或 CF(进位标志)的前提下做加法,必须用 LEA。
  3. 节省指令:正如上面所说,它可以一条指令完成 (A + B * C + D) 这样的计算。

五、 常见限制

虽然 LEA 很强大,但它必须遵守地址计算的语法: * 不能有两处变址:lea rax, [rbx * 2 + rcx * 2] 是不行的。 * 比例因子有限制:只能是 1, 2, 4, 8(因为内存寻址只支持这些步长)。 * 寄存器大小必须匹配:在 64 位模式下,你通常需要使用 64 位寄存器来计算。

总结

  • LEA 是算地址的,它不读内存。
  • LEA 是做数学题的,它能飞快地算加法和有限的乘法。
  • 看到 [] 时:如果是 MOV,那是去那儿取东西;如果是 LEA,那是算出那个位置在哪儿。

你现在理解为什么在反汇编代码里,经常看到 lea rax, [rbp - 0x10] 这种操作了吗?它其实是在获取局部变量的地址。