LEA
LEA 指令的全称是 Load Effective Address(加载有效地址)。
它是汇编语言中极其常用、且有点“不务正业”的指令。虽然它的本意是计算内存地址,但程序员经常用它来做快速算术运算。
为了让你彻底理解,我们从它的基本用法、与 MOV 的区别以及它的“副业”三个方面来讲解。
一、 基本用法:计算地址
LEA 的语法格式是:lea destination, [source]
它的作用是:计算 [source] 里的地址公式,把结果存入 destination,但不去访问内存。
示例:
* 动作:取出rbx 寄存器的值,加上 8。
* 结果:把这个计算结果直接存入 rsi。
* 重点:它没有去内存里看 [rbx + 8] 这个地方存了什么,它只是算出了这个地址本身。
二、 LEA vs MOV:核心区别
这是初学者最容易混淆的地方。请看下面这两行代码:
-
MOV指令(取包裹):- 它计算出
0x1000 + 8 = 0x1008。 - 它去内存
0x1008的位置看了一眼,发现里面存的是0x55。 - 结果:
rax = 0x55。
- 它计算出
-
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
这比 add 指令更灵活,因为 add 会修改操作数本身(比如 add rbx, rcx 会改变 rbx),而 lea 可以保持原值不变,把结果存到新寄存器里。
场景 2:实现 x = y * 5 + 10
利用比例因子和加法:
*rbx + rbx * 4 相当于 rbx * 5。
* 整条指令一步到位完成了乘法和加法。
* 优点:速度极快,比执行专门的 MUL 指令效率高得多。
四、 为什么要用 LEA?
- 取指针:在 C 语言里,当你写
p = &arr[i];时,编译器后端通常会生成一条LEA指令,算出数组元素的地址给指针。 - 不影响标志位:
LEA是纯粹的地址计算,它不会改变标志寄存器(RFLAGS)。- 如果你想在不破坏
ZF(零标志)或CF(进位标志)的前提下做加法,必须用LEA。
- 如果你想在不破坏
- 节省指令:正如上面所说,它可以一条指令完成
(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] 这种操作了吗?它其实是在获取局部变量的地址。