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


当前回答

如果有人已经提到了,请原谅我,但如果有人想知道x86糟糕的旧时代,内存分割仍然是相关的:您将始终从以下两条指令中得到相同的结果:

LEA AX, DS:[0x1234]

and

LEA AX, CS:[0x1234]

“有效地址”只是seg:off逻辑地址的偏移部分。在本例中,0x1234。

LEA不添加段基础。这将击败最初的一个用例,即进行地址计算以获得指针(偏移量),实际上可以取消引用。例如lea bx,[array+si]。如果添加了DS基以给出线性地址,则稍后的mov ax,[bx]将再次添加DS基。此外,20位结果通常不适合16位寄存器。

看见https://www.stevemorse.org/8086/index.html8086的建筑师写了一本关于指令集的书,现在在他的网站上免费。关于LEA的部分提到了他的一些设计意图。

其他回答

lea是“加载有效地址”的缩写。它将源操作数的位置引用地址加载到目标操作数。例如,您可以使用它:

lea ebx, [ebx+eax*8]

用一条指令进一步移动ebx指针eax项(在64位/元素数组中)。基本上,您可以从x86体系结构支持的复杂寻址模式中受益,从而有效地操作指针。

来自Abrash的“装配禅”:

LEA,唯一执行内存寻址计算但实际上不寻址内存的指令。LEA接受标准内存寻址操作数,但只会将计算出的内存偏移量存储在指定寄存器中,该寄存器可以是任何通用寄存器。这给了我们什么?ADD没有提供的两件事:使用两个或三个操作数执行加法的能力,以及将结果存储在任何寄存器中的能力;而不仅仅是源操作数之一。

执法机关不改变旗帜。

示例

LEA EAX,[EAX+EBX+1234567]计算EAX+EBX+134567(即三个操作数)LEA EAX,[EBX+ECX]计算EBX+ECX,而不使用结果覆盖两者。乘以常数(乘以2、3、5或9),如果你像LEA EAX那样使用,[EBX+N*EBX](N可以是1,2,4,8)。

其他用例在循环中很方便:LEA EAX、[EAX+1]和INC EAX之间的区别在于后者更改EFLAGS,但前者不更改;这保持了CMP状态。

这里有一个例子。

// 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(负载有效地址)经常被用作进行某些计算的“技巧”,但这并不是它的主要目的。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 vs MOV(回复原始问题)

LEA不是一个时髦的MOV。当您使用MOV时,它会计算地址并访问内存。LEA只计算地址,实际上不访问内存。这就是区别。

在8086及更高版本中,LEA仅将最多两个源寄存器和一个立即数的和设置为目标寄存器。例如,lea-bp,[bx+si+3]将bx加si加3的和设置到bp寄存器。无法实现此计算以将结果保存到带有MOV的寄存器中。

80386处理器引入了一系列缩放模式,其中索引寄存器值可以乘以有效的缩放因子以获得位移。有效比例因子为1、2、4和8。因此,您可以使用lea-ebp、[ebx+esi*8+3]等指令。

LDS和LES(可选进一步阅读)

与LEA相反,有指令LDS和LES,相反,它们将值从内存加载到一对寄存器:一个段寄存器(DS或ES)和一个通用寄存器。其他寄存器也有版本:分别用于FS、GS和SS段寄存器的LFS、LGS和LSS(80386中介绍)。

因此,这些指令加载“远”指针-一个由16位段选择器和16位(或32位,取决于模式)偏移量组成的指针,因此在16位模式下,远指针的总大小为32位,在32位模式下为48位。

这些是16位模式的方便说明,无论是16位实际模式还是16位保护模式。

在32位模式下,由于操作系统将所有段基设置为零(平面内存模型),所以不需要加载这些指令,因此不需要加载段寄存器。我们只使用32位指针,而不是48位指针。

在64位模式下,不执行这些指令。它们的操作码会导致访问违规中断(异常)。自从Intel实施VEX“矢量扩展”(AVX)以来,Intel采用了LDS和LES的操作码,并开始将它们用于VEX前缀。正如Peter Cordes所指出的,这就是为什么在32位模式下只能访问x/ymm0..7的原因(引用):“VEX前缀经过精心设计,仅与32位模式中LDS和LES的无效编码重叠,其中RÜxÜBÜ均为1。这就是为什么某些位在VEX前缀中被反转的原因”。