LEA指令的目的是什么?

对我来说,这只是一个时髦的MOV。 它的目的是什么?我什么时候可以使用它?

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

struct Point { int xcoord; int ycoord; }; 

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

 int y = points[i].ycoord; 

其中points[]是一个Point数组。 假设数组的基数已经在EBX ,variablesiEAXxcoordycoord每个都是32位(所以ycoord在结构中的偏移量是4个字节),这个语句可以被编译为:

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

这将在EDX登陆。 比例因子为8是因为每个Point大小是8个字节。 现在考虑与“地址”运算符&相同的expression式:

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

在这种情况下,你不想要ycoord的价值,但它的地址。 这就是LEA (加载有效地址)的地方。编译器可以生成,而不是MOV

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

这将在ESI加载地址。

从Abrash的“集会之禅”

LEA是执行内存寻址计算的唯一指令,但实际上并不寻址内存。 LEA接受一个标准的内存寻址操作数,只不过是将计算出的内存偏移量存储在指定的寄存器中,可以是任何通用寄存器。

这给了我们什么? ADD没有提供的两件事:

  1. 用两个或三个操作数执行加法的能力
  2. 将结果存储在任何寄存器中的能力; 不只是源操作数之一。

LEA不会改变标志。

LEA指令的另一个重要特性是它不会改变条件代码(如CF和ZF),而用ADD或MUL等算术指令来计算地址。 这个特性降低了指令之间的依赖关系,从而为编译器或者硬件调度程序的进一步优化留下了空间。

尽pipe所有的解释,LEA是一个算术运算:

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

这只是它的名字是非常愚蠢的转移+添加操作。 其原因已经在最受好评的答案中解释过了(即它被devise为直接映射高级内存引用)。

也许只是关于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 

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

 lea ebx, [ebx+eax*8] 

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

MOV使用LEA的最大原因是如果您需要对正在使用的寄存器执行算术计算地址。 实际上,对于“自由”,您可以有效地执行相当于几个寄存器上指针运算的组合。

真正令人困惑的是,你通常像一个MOV一样编写一个LEA ,但是实际上并不提取内存。 换一种说法:

MOV EAX, [ESP+4]

这将把ESP+4指向EAX

LEA EAX, [EBX*8]

这会将有效地址EBX * 8移到EAX中,而不是在那个地方find的东西。 正如你所看到的,也可以乘以两个因子(缩放),而MOV限于加/减。

8086有一个大的指令系列,它们接受一个寄存器操作数和一个有效地址,执行一些计算来计算该有效地址的偏移部分,并执行一些涉及计算地址所指的寄存器和存储器的操作。 除了跳过实际的内存操作之外,让这个系列中的一条指令具有上述行为相当简单。 这个,说明:

   mov ax,[bx + si + 5]
  利斧,[bx + si + 5]

在内部几乎完全相同。 所不同的是跳过了一步。 两条指令都是这样工作的:

   temp =取得立即数操作数(5)
   temp + = bx
   temp + = si
   address_out = temp(LEA跳过)
  触发16位读取(跳过LEA)
   temp = data_in(跳过LEA)
   ax = temp

至于为什么英特尔认为这个指令是值得包括的,我并不确定,但是实施起来便宜的事实将是一个很大的因素。 另一个因素是,英特尔的汇编程序允许相对于BP寄存器定义符号。 如果fnord被定义为BP相对符号(例如BP + 8),则可以这样说:

   mov ax,fnord; 相当于“mov ax,[BP + 8]”

如果有人想使用像stosw这样的东西来存储数据到一个BP相对地址,可以这样说

   mov ax,0; 要存储的数据
   mov cx,16; 字数
   lea di,fnord
   rep movs fnord; 地址被忽略除了注意到它是一个SS相关字ptr

比以下方便:

   mov ax,0; 要存储的数据
   mov cx,16; 字数
   mov di,bp
  加di,抵消fnord(即8)
   rep movs fnord; 地址被忽略除了注意到它是一个SS相关字ptr

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

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

有关LEA单元的详细信息,请查阅Haswell架构的这篇文章: http : //www.realworldtech.com/haswell-cpu/4/

其他答案中没有提到的另一个重点是LEA REG, [MemoryAddress]指令是PIC(位置无关的代码),它在本指令中对PC相对地址进行编码以引用MemoryAddress 。 这与MOV REG, MemoryAddress不同MOV REG, MemoryAddress后者编码相对虚拟地址,并且需要在现代操作系统中进行重定位/修补(如ASLR是常见function)。 所以LEA可以用来把这种非PIC转换成PIC。

LEA指令可以用来避免由CPU耗费时间计算有效地址。 如果重复使用地址,则将其存储在寄存器中更有效,而不是每次使用时计算有效地址。

这是一个例子。

 // 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(优化)作为编译器选项,gcc将查找指示代码行的指令。

LEA:只是一个“算术”指令..

MOV在操作数之间传输数据,但是只是计算

LEA指令是一种获取任何英特尔处理器内存寻址模式所产生的地址的方法。

也就是说,如果我们有这样的数据移动:

 MOV EAX, <MEM-OPERAND> 

它将指定内存位置的内容移动到目标寄存器中。

如果我们用LEAreplaceMOV ,那么通过<MEM-OPERAND>寻址expression式以完全相同的方式计算存储单元的地址。 但是,而不是内存位置的内容,我们得到的位置本身到目的地。

LEA不是一个特定的算术指令; 它是一种截取处理器内存寻址模式中的任何一种有效地址的方法。

例如,我们可以在简单的直接地址上使用LEA 。 根本不涉及算术:

 MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX. 

这是有效的; 我们可以在Linux提示符下进行testing:

 $ as LEA 0, %eax $ objdump -d a.out a.out: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <.text>: 0: 8d 04 25 00 00 00 00 lea 0x0,%eax 

这里没有增加缩放值,也没有偏移量。 零被移入EAX。 我们可以用MOV和立即操作数来做到这一点。

这就是为什么那些认为LEA中的括号是多余的人是严重错误的; 括号不是LEA语法,但是是寻址模式的一部分。

LEA在硬件层面是真实的。 生成的指令对实际寻址模式进行编码,处理器将其输出到计算地址的位置。 然后它将该地址移动到目的地而不是生成内存引用。 (由于其他指令的寻址模式的地址计算对CPU标志没有影响,所以LEA对CPU标志没有影响。)

与加载地址零的值相反:

 $ as movl 0, %eax $ objdump -d a.out | grep mov 0: 8b 04 25 00 00 00 00 mov 0x0,%eax 

这是一个非常相似的编码,看? 只是LEA8d已经变成了8b

当然,这个LEA编码比将一个立即的零移动到EAX更长:

 $ as movl $0, %eax $ objdump -d a.out | grep mov 0: b8 00 00 00 00 mov $0x0,%eax 

LEA没有理由排除这种可能性,只是因为有一个更短的select; 它只是以正交的方式与可用的寻址模式组合。

这是因为你写代码

 mov dx,offset something 

你可以简单地写

 lea dx,something