LEA指令的目的是什么?


Answers:


795

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

struct Point
{
     int xcoord;
     int ycoord;
};

现在想象一条语句:

int y = points[i].ycoord;

points[]的数组在哪里Point?假设阵列的基础已经在EBX和可变i是在EAX,并且xcoordycoord各自的32位(以便ycoord在在struct偏移4个字节),该语句可以被编译成:

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

它将降落yEDX。比例因子8是因为每个Point大小为8个字节。现在考虑与运算符&的“地址”一起使用的表达式:

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

在这种情况下,您不需要的值ycoord,而是它的地址。这就是LEA(加载有效地址)的来源。MOV编译器可以生成而不是,

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

这将在中加载地址ESI


112
扩展mov说明并放在方括号之外会更干净吗?MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake通过用专用的MOV替换LEA,可以使语法保持整洁:[]方括号始终等效于在C中解引用指针。如果没有括号,则始终会处理指针本身。
Natan Yellin

139
在MOV指令(EBX + 8 * EAX + 4)中进行数学运算是无效的。LEA ESI [EBX + 8 * EAX + 4]有效,因为这是x86支持的寻址模式。en.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JonathanDickinson LEA类似于MOV带有间接源的,不同之处在于它仅执行间接操作而不执行MOV。它实际上并不计算出的地址中读取,而只是对其进行计算。
2013年

24
埃里克(Erik),游览评论不正确。MOV eax [ebx + 8 * ecx + 4]有效。但是,MOV返回该存储位置的内容,而LEA返回该地址
Olorin 2015年

561

摘自Abrash 的“汇编禅”

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

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

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

并且LEA不会更改标志。

例子

  • LEA EAX, [ EAX + EBX + 1234567 ]计算EAX + EBX + 1234567(这是三个操作数)
  • LEA EAX, [ EBX + ECX ]计算时EBX + ECX不会覆盖任何结果。
  • 如果您像这样使用LEA EAX, [ EBX + N * EBX ](N可以是1,2,4,8),则可以乘以常数(乘以2、3、5 或9 )。

其他的用例是在循环中派上用场:之间的区别LEA EAX, [ EAX + 1 ],并INC EAX是后者的变化EFLAGS,但前者没有; 这样可以保留CMP状态。


42
@AbidRahmanK一些例子:LEA EAX, [ EAX + EBX + 1234567 ]计算的总和EAXEBX1234567(这是三个操作数)。LEA EAX, [ EBX + ECX ]计算时EBX + ECX 不会覆盖任何结果。第三个LEA用于(未在Frank列出)的是乘以常数乘以 2、3、5 或9),如果您使用它的话LEA EAX, [ EBX + N * EBX ]N可以是1,2,4,8)。其他的用例是在循环中派上用场:之间的区别LEA EAX, [ EAX + 1 ],并INC EAX是后者的变化EFLAGS,但前者没有; 这保留了CMP状态
FrankH。

@FrankH。我还是不明白,所以它将指针加载到其他地方吗?

6
@ ripDaddy69是的,有点-如果“加载”是指“执行地址计算/指针算术”。它不访问内存(即不“解引用”用C编程术语调用的指针)。
FrankH。

2
+1:这明确说明了哪些类型的“技巧” LEA可以用于...(请参阅上面IJ Kennedy的流行答案中的“ LEA(有效负载地址)经常被用作进行某些计算的“技巧”))
阿萨德Ebrahim

3
快速的2个操作数LEA和缓慢的3个操作数LEA之间有很大的区别。英特尔优化手册说,快速路径LEA是单周期,而慢速路径LEA需要三个周期。此外,在Skylake上,有两个快速路径功能单元(端口1和5),而只有一个慢路径功能单元(端口1)。手册中的汇编/编译器编码规则33甚至警告不要使用3个操作数LEA。
Olsonist,

110

LEA指令的另一个重要特征是,在通过像或那样的算术指令计算地址时,它不会更改条件代码(例如CF和)。此功能降低了指令之间的依赖性,从而为编译器或硬件调度程序的进一步优化留出了空间。ZFADDMUL


1
是的,lea有时对于编译器(或人工编码器)进行数学运算而不会破坏标志结果很有用。但是lea并没有比快add。大多数x86指令都写标志。高性能x86实现必须重命名EFLAGS,否则将避免正常代码快速运行后的写后写入危险,因此避免标志写入的指令因此并不是最好的选择。(部分标记的内容可能会造成问题,请参阅INC指令与ADD 1:有关系吗?
Peter Cordes

2
@PeterCordes:讨厌在这里提出这个问题,但是-我是否一个人在想这个新的[x86-lea]标签是多余的和不必要的?
Michael Petch

2
@MichaelPetch:是的,我认为这太具体了。似乎使不懂机器语言的初学者感到迷惑,并且所有内容(包括指针)都只是位/字节/整数,因此,关于它的大量投票有很多问题。但是为其加上标签意味着未来的问题有无限的余地,而实际上总共有2或3个不只是重复项。(这是什么?如何将其用于整数乘法?以及它如何在AGU与ALU之间内部运行以及具有什么延迟/吞吐量。也许是“预期的”目的)
Peter Cordes

@PeterCordes:我同意,所有这些正在编辑的帖子,如果有的话,几乎都是与LEA相关的一些现有问题的副本。不应使用标签,而应标识任何重复项并标记为恕我直言。
Michael Petch

1
@EvanCarroll:如果尚未完成,请继续标记所有LEA问题。如上所述,我们认为x86-lea对于标签来说太具体了,将来没有重复的问题没有太多的范围。我认为,实际上要为大多数人选择“最佳” Q&A作为重复目标,或者实际决定要让哪些Mod合并将是很多工作。
彼得·科德斯

93

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

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

只是它的名称对于shift + add操作极其愚蠢。评分最高的答案中已经说明了其原因(即,它旨在直接映射高级内存引用)。


8
并且该算法是由地址计算硬件执行的。
Ben Voigt

30
@BenVoigt我以前是这么说的,因为我是个老家伙:-)传统上,x86 CPU确实为此使用了寻址单元。但是这些天的“分离”变得非常模糊。一些CPU根本不再具有专用的 AGU,其他CPU 选择不在LEAAGU上执行,而是在普通整数ALU上执行。这些天,人们必须非常仔细地阅读CPU规格,以找出“运行的地方” ...
FrankH。

2
@FrankH .:乱序的CPU通常在ALU上运行LEA,而某些乱序的CPU(例如Atom)有时在AGU上运行LEA(因为它们不能忙于处理内存访问)。
彼得·科德斯

3
不,这个名字并不愚蠢。 LEA为您提供由任何与存储器相关的寻址模式产生的地址。这不是移位和加法运算。
卡兹(Kaz),

3
FWIW目前很少有(如果有的话)在AGU上执行操作的x86 CPU。像其他任何算术运算一样,大多数或全部仅使用ALU。
BeeOnRope

77

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

13
+1。但是我想问一个问题(可能是愚蠢的),为什么不这样直接乘以三LEA EAX, [EAX*3]呢?
Abid Rahman K

13
@Abid Rahman K:没有诸如x86 CPU指令集之类的指令。
GJ。

50
尽管Intel asm语法使@AbidRahmanK看起来像一个乘法,但lea指令只能对移位操作进行编码。操作码有2位来描述移位,因此,可以只乘以由1,2,4或8
ithkuil

6
@Koray Tugay:您可以像shl指令一样使用左移,将寄存器乘以2,4,8,16 ...它更快,更短。但是对于乘以2的幂次方不同的数字,我们通常使用mul更自命不凡和较慢的指令。
GJ。

7
@GJ。尽管没有这种编码,但是一些汇编程序将其视为快捷方式,例如fasm。因此,例如lea eax,[eax*3]将转换为的等价物lea eax,[eax+eax*2]
Ruslan

59

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

lea ebx, [ebx+eax*8]

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


23

LEA在a MOV上使用的最大原因是是否需要对要用来计算地址的寄存器执行算术运算。有效地,您可以有效地对多个寄存器组合使用相当于“免费”的指针算术运算。

真正令人困惑的是,您通常写的LEA像a,MOV但是实际上并没有取消引用内存。换一种说法:

MOV EAX, [ESP+4]

这将把ESP+4指向的内容移到EAX

LEA EAX, [EBX*8]

这会将有效地址EBX * 8移入EAX,而不是该位置中找到的地址。如您所见,也可以乘以2的因子(缩放比例),而a MOV仅限于加/减。


不好意思 @ big.heart在三个小时前给了我一个答案,这使我愚蠢,使它在我的Assembly问题搜索中显示为“ new”。
David Hoelzer

1
为什么不执行内存寻址时语法为何使用括号?
golopot

3
@ q4w56这就是答案之一,“这就是您的操作方式”。我相信,这是人们很难弄清楚该做什么的原因之一LEA
David Hoelzer's

2
@ q4w56:这是使用内存操作数语法机器代码编码的shift + add指令。在某些CPU上,它甚至可能使用AGU硬件,但这是一个历史细节。仍然相关的事实是,已经有解码器硬件可用于解码这种shift + add,而LEA让我们将其用于算术运算而不是存储器寻址。(或者用于地址计算(如果一个输入实际上是一个指针))。
彼得·科德斯

20

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

至于为什么英特尔认为该指令值得包括在内,我不确定,但是实施起来便宜的事实将是一个很大的因素。另一个因素可能是英特尔的汇编程序允许相对于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。哎呀。


12

如现有答案所述,它LEA具有以下优点:无需访问存储器即可执行存储器寻址算术,将算术结果保存到其他寄存器中,而不是简单的添加指令形式。真正的潜在性能优势是,现代处理器具有单独的LEA ALU单元和端口,可以有效地生成地址(包括LEA和其他内存参考地址),这意味着LEAALU中的算术运算和其他正常算术运算可以并行进行核心。

查看本文的Haswell体系结构以获取有关LEA单元的一些详细信息:http : //www.realworldtech.com/haswell-cpu/4/

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


2
“单独的LEA ALU”部分大部分是不正确的。现代CPU lea在执行其他算术指令的一个或多个相同的ALU上执行(但通常少于其他算术指令)。例如,提到的Haswell CPU可以在四个不同的 ALU 上执行addsub大多数其他基本的算术运算,但只能在一个(复杂)或两个(简单)上执行。更重要的是,具有这两个功能的ALU只是可以执行其他指令的四个中的两个,因此没有所要求的并行性优势。lealealealea
BeeOnRope

您链接的文章(正确)显示LEA与整数ALU(加/减/布尔)和Haswell中的整数MUL单元位于同一端口上。(以及矢量ALU,包括FP ADD / MUL / FMA)。仅简单的LEA单元位于端口5上,该端口还可以运行ADD / SUB /其他功能,以及矢量随机播放和其他功能。我不拒绝投票的唯一原因是您指出了使用相对RIP的LEA(仅适用于x86-64)。
彼得·科德斯

8

LEA指令可用于避免CPU耗时的有效地址计算。如果地址被重复使用,则将其存储在寄存器中会比每次使用该地址都更有效,而不是计算有效地址。


不一定在现代x86上。大多数寻址模式的成本相同,但有一些警告。因此[esi]很少比说便宜,[esi + 4200]而且很少比说便宜[esi + ecx*8 + 4200]
BeeOnRope

@BeeOnRope [esi]并不比便宜[esi + ecx*8 + 4200]。但是为什么要比较呢?它们不相等。如果希望前者指定与后者相同的内存位置,则需要其他说明:您必须将乘以8 esi的值加起来ecx。哦,哦,乘法将破坏您的CPU标志!然后,您必须添加4200。这些其他指令会增加代码大小(占用指令高速缓存中的空间,以及获取周期)。
卡兹(Kaz),

2
@Kaz-我认为您错过了我的观点(否则我错过了OP的观点)。我的理解是,OP表示如果要[esi + 4200]在一系列指令中重复使用类似的东西,那么最好先将有效地址加载到寄存器中并使用它。例如,add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]您最好选择,而不是写作lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi],它通常不会更快。至少那是对这个答案的简单解释。
BeeOnRope

所以我比较的原因[esi][esi + 4200](或[esi + ecx*8 + 4200]为这是OP提议简化(据我所知):具有相同复杂的地址N个指令转化为简单的(一个REG)寻址,加上一个N个指令lea,因为复杂的寻址是“耗时的”,实际上,即使在现代的x86上,它也较慢,但仅在延迟方面,这似乎对于具有相同地址的连续指令似乎不太重要
BeeOnRope

1
也许可以缓解某些寄存器压力,是的-但情况可能恰恰相反:如果使用有效地址生成寄存器的寄存器处于活动状态,则需要另一个寄存器来保存结果,lea因此在这种情况下会增加压力。一般而言,储存中间体是引起压力升高的原因,而不是解决方案的压力-但我认为在大多数情况下,这是一种洗礼。@Kaz
BeeOnRope

7

LEA(加载有效地址)指令是一种获取地址的方法,该地址源自任何英特尔处理器的内存寻址模式。

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

MOV EAX, <MEM-OPERAND>

它将指定存储单元的内容移至目标寄存器。

如果我们将MOVby 替换为LEA,则通过<MEM-OPERAND>寻址表达式以完全相同的方式计算内存位置的地址。但是,除了存储位置的内容外,我们还可以将存储位置本身放入目标位置。

LEA不是特定的算术指令;这是一种拦截由于处理器的任何一种内存寻址模式而产生的有效地址的方法。

例如,我们可以LEA仅使用一个简单的直接地址。完全不涉及算术:

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

这是有效的;我们可以在Linux提示符下对其进行测试:

$ 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

这是非常相似的编码,看到了吗?正义8dLEA已更改为8b

当然,这种LEA编码比将立即数零移动到的时间更长EAX

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

没有理由LEA排除这种可能性,尽管仅仅是因为有一个更短的选择。它只是以正交方式与可用的寻址模式结合在一起。


6

这是一个例子。

// 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指令。


6

似乎许多答案已经完成,我想再添加一个示例代码,以显示lea和move指令在具有相同表达式格式时如何不同地工作。

长话短说,lea指令和mov指令都可以与括号内的src操作数一起使用。当它们括与() ,在表达()以同样的方式被计算; 但是,两条指令将以不同的方式解释src操作数中的计算值。

无论表达式是与lea还是mov一起使用,src值都将按以下方式计算。

D(Rb,Ri,S) => (Reg [Rb] + S * Reg [Ri] + D)

但是,与mov指令一起使用时,它将尝试访问由上述表达式生成的地址所指向的值,并将其存储到目的地。

与此相反,当使用上述表达式执行lea指令时,它会将生成的值直接加载到目标位置。

以下代码使用相同的参数执行lea指令和mov指令。但是,为了弥补差异,我添加了一个用户级信号处理程序来捕获由于mov指令导致访问错误地址而导致的分段错误。

范例程式码

#define _GNU_SOURCE 1  /* To pick up REG_RIP */
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>


uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
        uint32_t ret = 0;
        struct sigaction act;

        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
        ret = sigaction(event, &act, NULL);
        return ret;
}

void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
        ucontext_t *context = (ucontext_t *)(priv);
        uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
        uint64_t faulty_addr = (uint64_t)(info->si_addr);

        printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
                rip,faulty_addr);
        exit(1);
}

int
main(void)
{
        int result_of_lea = 0;

        register_handler(SIGSEGV, segfault_handler);

        //initialize registers %eax = 1, %ebx = 2

        // the compiler will emit something like
           // mov $1, %eax
           // mov $2, %ebx
        // because of the input operands
        asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
            :"=d" (result_of_lea)   // output in EDX
            : "a"(1), "b"(2)        // inputs in EAX and EBX
            : // no clobbers
         );

        //lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
        printf("Result of lea instruction: %d\n", result_of_lea);

        asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
                       :
                       : "a"(1), "b"(2)
                       : "edx"  // if it didn't segfault, it would write EDX
          );
}

执行结果

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1
将内联汇编拆分成单独的语句是不安全的,并且掩体列表也不完整。basic-asm块告诉编译器没有Clobbers,但实际上它修改了几个寄存器。同样,您可以=d用来告诉编译器结果在EDX中,并保存一个mov。您还忽略了输出的早期提示。这确实演示了您要演示的内容,但这也是一个误导性的内联汇编错误示例,如果在其他情况下使用该内联汇编,它将损坏。对于堆栈溢出答案,这是一件坏事。
彼得·科德斯

如果您不想%%在扩展asm中写所有这些寄存器名称,请使用输入约束。喜欢asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));。让编译器初始化寄存器意味着您也不必声明Clobbers。在mov-immediate覆盖整个寄存器之前,您还需要通过异或归零来使事情复杂化。
彼得·科德斯

@PeterCordes谢谢,彼得,您要我删除此答案还是根据您的评论对其进行修改?
Jaehyuk Lee

1
如果您修复了内联汇编,它不会造成任何危害,并且可能为不了解其他答案的初学者提供了一个很好的具体示例。无需删除,这很容易解决,就像我在上一条评论中所示。我认为,如果将内联汇编的错误示例固定为“良好”示例,那将是值得赞扬的。(我没有拒绝投票)
Peter Cordes

1
有人在哪里说那mov 4(%ebx, %eax, 8), %edx是无效的?无论如何,是的,因为mov"a"(1ULL)它告诉编译器您有一个64位的值是有意义的,因此需要确保扩展它以填充整个寄存器。实际上,它仍然会使用mov $1, %eax,因为将EAX零扩展写入RAX中,除非您对周围的代码感到奇怪的情况,其中编译器知道RAX = 0xff00000001或其他含义。对于lea,您仍在使用32位操作数大小,因此输入寄存器中的任何杂散高位都不会影响32位结果。
彼得·科德斯

4

LEA:只是“算术”指令。

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


LEA显然会移动数据。它有一个目标操作数。LEA并不总是计算;它计算源操作数中表示的有效地址是否计算。LEA EAX,GLOBALVAR不计算;它只是将GLOBALVAR的地址移到EAX中。
卡兹(Kaz),

@Kaz感谢您的反馈。我的消息来源是“ LEA(加载有效地址)本质上是一条算术指令-它不执行任何实际的内存访问,但是通常用于计算地址(尽管您可以使用它来计算通用整数)。” 形式Eldad-Eilam的书第149页
会计师

@Kaz:这就是为什么当地址已经是链接时间常数时,LEA是多余的;使用mov eax, offset GLOBALVAR代替。您可以使用LEA,但它的代码大小略大一些,mov r32, imm32并且可以在更少的端口上运行,因为它仍然需要经过地址计算过程lea reg, symbol仅当需要PIC和/或低32位以下的地址时,才在64位中用于相对RIP的LEA。在32位或16位代码中,优势为零。LEA是一种算术指令,可显示CPU解码/计算寻址模式的能力。
彼得·科德斯

@Kaz:使用相同的论点,您可能会说这imul eax, edx, 1不是计算结果:它只是将edx复制到eax。但实际上,它通过乘法器以3个周期的延迟运行数据。或rorx eax, edx, 0只是复制(旋转零)。
彼得·科德斯

@PeterCordes我的观点是,LEA EAX,GLOBALVAL和MOV EAX,GLOBALVAR都只是从立即数中获取地址。没有乘数1或偏移量0被应用;在硬件级别上可能是这种方式,但是在汇编语言或指令集中却看不到。
卡兹

1

所有正常的“计算”指令(如加法,异或)或设置状态标志(如零,符号)。如果使用复杂的地址,AX xor:= mem[0x333 +BX + 8*CX] 则根据异或运算设置标志。

现在,您可能需要多次使用该地址。从未将这样的地址加载到寄存器中是为了设置状态标志,幸运的是,没有这样做。短语“加载有效地址”使程序员意识到这一点。那就是怪异表情的来源。

显然,一旦处理器能够使用复杂的地址来处理其内容,就可以将其用于其他目的。实际上,它可以用于x <- 3*x+1在一条指令中执行转换。这是汇编编程中的一条一般规则:使用说明,但会摇晃您的船。 唯一重要的是指令所体现的特定转换是否对您有用。

底线

MOV, X| T| AX'| R| BX|

LEA, AX'| [BX]

AX具有相同的作用,但对状态标志没有影响。(这是ciasdis符号。)


“这是汇编编程中的一条通用规则:请按照说明操作,但它会摇摇欲坠。” 由于诸如从call lbl lbl: pop rax技术上讲“正在工作”之类的方法来获取的价值,我不会亲自提供建议rip,但是您会使分支预测非常不愉快。使用所需的说明,但是如果您做一些棘手的事情也不要感到惊讶,它会带来您没有预料到的后果
The6P4C

@ The6P4C这是一个有用的警告。但是,如果除了让分支预测不满意之外别无选择,那就必须去做。汇编编程中还有另一个通用规则。可能有其他方法可以做某事,您必须从其他方法中明智地选择。有数百种方法可以将寄存器BL的内容放入寄存器AL中。如果不需要保留RAX的其余部分,则可以选择LEA。在数千种类型的x86处理器中,不影响标志可能是一个好主意。Groetjes Albert
阿尔伯特·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.