X86 "lea" 指令背后的直觉

创建时间: 2019年6月11日

一些朋友们在之前的北丹佛地区C++聚会上提到X86指令中的 lea 并不如其他的指令那么好理解。lea 是“load effective address”的缩写,一般来说被解释为“把某个内存地址从放到目标位置”。在Intel汇编语法下,lea 被写作

lea destination, source

举一个例子, 如果我们有一个 struct Point 的数组 points

struct Point
{
    int x;
    int y;
    int z;
};

当遇到 int x = points[i].y; 时,编译器可以生成如下的汇编:

mov  eax, [rbx+rcx*4 + 4]

在此行,寄存器 rbx 储存了数组 points 的地址,寄存器 rcx 代表 i ,而寄存器 eax 代表返回值 x。同理,编译器可将 int* x = &points[i].y; 编译为

lea  eax, [rbx+rcx*4 + 4]

编译器对于 lea 的妙用不止步与地址操作,它们也喜欢用 lea 进行算术操作。例如 int y = x * 5;就更有可能被编译成以下的形式:

lea     eax, [rdi + 4*rdi]

而不是下面的这种直接使用算术指令的形式。

imul    eax, [rdi], 5

我个人认为最好把 lea 理解成指针算术(pointer arithmetic)加上类型转换。如果用这种思维来看待前面的例子,那么相应的C代码是

int y = (int)(&((int*)x)[x]);

这段代码首先把 x 当成一个 int 指针 ( (int*)x ),然后得到该指针指向的第x个元素。这样我们就得到了地址 [rdi + 4*rdi]。接下来,我们把该地址的低32位作为一个数字移到终点 y

我希望该例能够给你一些对 lea 的直觉理解。当然,没有正常的C程序员会手写如上的代码 而在C++下该段代码更是非法(C++不允许把指针转换成小类型 int)。不过,从一个机器的角度,这种类型转换完全是免费的,而机器码经常会进行这种操作。