对我来说,这就像一部时髦的电影。它的用途是什么?我应该什么时候使用它?


当前回答

正如前面提到的现有答案,LEA具有执行内存寻址运算而不访问内存的优点,将运算结果保存到不同的寄存器,而不是简单形式的加法指令。真正的潜在性能优势是现代处理器有一个单独的LEA ALU单元和端口,用于有效的地址生成(包括LEA和其他内存参考地址),这意味着LEA中的算术运算和ALU中的其他正常算术运算可以在一个核中并行完成。

查看Haswell架构的这篇文章,了解LEA单元的一些详细信息:http://www.realworldtech.com/haswell-cpu/4/

其他答案中未提及的另一个重要点是LEA REG,[MemoryAddress]指令是PIC(位置无关代码),它将此指令中的PC相对地址编码为参考MemoryAddress。这不同于MOV REG,MemoryAddress编码相对虚拟地址,需要在现代操作系统中重新定位/修补(如ASLR是常见功能)。因此,LEA可用于将非PIC转换为PIC。

其他回答

正如其他人所指出的,LEA(负载有效地址)经常被用作进行某些计算的“技巧”,但这并不是它的主要目的。x86指令集是为支持Pascal和C等高级语言而设计的,在这些语言中,数组特别是int数组或小型结构是常见的。例如,考虑表示(x,y)坐标的结构:

struct Point
{
     int xcoord;
     int ycoord;
};

现在想象一下这样的陈述:

int y = points[i].ycoord;

其中points[]是Point的数组。假设数组的基已经在EBX中,变量i在EAX中,xcoord和ycoord各为32位(因此ycoord在结构中的偏移量为4字节),则该语句可以编译为:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

其将在EDX中降落y。比例因子为8是因为每个点的大小为8字节。现在考虑与“address of”运算符使用的相同表达式&:

int *p = &points[i].ycoord;

在这种情况下,您不需要ycoord的值,而是需要它的地址。这就是LEA(加载有效地址)的作用

LEA ESI, [EBX + 8*EAX + 4]

这将在ESI中加载地址。

尽管有各种解释,LEA是一种算术运算:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

只是它的名字对于shift+add操作来说非常愚蠢。其原因已经在最高评级的答案中解释过(即,它是为了直接映射高级内存引用而设计的)。

这里有一个例子。

// compute parity of permutation from lexicographic index
int parity (int p)
{
  assert (p >= 0);
  int r = p, k = 1, d = 2;
  while (p >= k) {
    p /= d;
    d += (k << 2) + 6; // only one lea instruction
    k += 2;
    r ^= p;
  }
  return r & 1;
}

使用-O(optimize)作为编译器选项,gcc将找到指定代码行的lea指令。

也许只是LEA指令的另一件事。您还可以使用LEA将寄存器快速乘以3、5或9。

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

8086有一大系列指令,它们接受寄存器操作数和有效地址,执行一些计算以计算该有效地址的偏移部分,并执行一些涉及寄存器和由计算地址引用的存储器的操作。除了跳过实际的内存操作之外,让该家族中的一个指令的行为与上面一样非常简单。因此,说明:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

在内部实现几乎相同。区别在于跳过了一步。这两个指令的作用类似于:

temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp  (skipped for LEA)
trigger 16-bit read  (skipped for LEA)
temp = data_in  (skipped for LEA)
ax = temp

至于英特尔为什么认为这条指令值得包括在内,我并不完全确定,但它的实现成本低是一个重要因素。另一个因素是Intel的汇编器允许相对于BP寄存器定义符号。如果fnord被定义为BP相对符号(例如BP+8),可以说:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

如果想使用stosw之类的东西将数据存储到BP的相对地址

mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

比:

mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

注意,忘记世界“偏移”将导致位置[BP+8]的内容而不是值8被添加到DI中。哎呀。