LEA指令的目的是什么?

use*_*557 632 x86 assembly x86-64 x86-16

对我来说,它看起来像一个时髦的MOV.它的目的是什么,我什么时候应该使用它?

I. *_*edy 756

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

struct Point
{
     int xcoord;
     int ycoord;
};
Run Code Online (Sandbox Code Playgroud)

现在想象一下如下声明:

int y = points[i].ycoord;
Run Code Online (Sandbox Code Playgroud)

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

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"
Run Code Online (Sandbox Code Playgroud)

这将降落yEDX.比例因子为8是因为每个Point大小为8个字节.现在考虑与"address of"运算符一起使用的相同表达式:

int *p = &points[i].ycoord;
Run Code Online (Sandbox Code Playgroud)

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

LEA ESI, [EBX + 8*EAX + 4]
Run Code Online (Sandbox Code Playgroud)

这将加载地址ESI.

  • 在MOV指令(EBX + 8*EAX + 4)中进行数学运算无效.LEA ESI,[EBX + 8*EAX + 4]有效,因为这是x86支持的寻址模式.http://en.wikipedia.org/wiki/X86#Addressing_modes (133认同)
  • 扩展`mov`指令并从括号中删除它不是更清晰吗?`MOV EDX,EBX + 8*EAX + 4` (98认同)
  • @JonathanDickinson LEA就像一个带有间接源的`MOV`,除了它只做间接而不是`MOV`.它实际上并不*从*计算地址读取,只是计算它. (27认同)
  • Erik,巡回评论不准确.MOV eax,[ebx + 8*ecx + 4]有效.但是,MOV返回内存位置的内容,而LEA返回地址 (22认同)
  • @imacake通过用专门的MOV替换LEA,你可以保持语法清晰:[]括号总是相当于在C中取消引用指针.没有括号,你总是处理指针本身. (14认同)
  • 我仍然不明白为什么需要括号.对于EBX + 8*EAX + 4中存储的内容,`LEA`不做任何事情; 它只是"加载存储在那里的任何东西的地址"......就像在做`ptr2 =&(*ptr1)`.过于复杂的基本上只是另一个算术运算. (5认同)
  • @EmmaHe 这取决于您使用的汇编器。如果汇编器使用 Intel 语法(例如 MASM、NASM、FASM),则等效为“mov dest src”,但如果汇编器遵循 AT&T 语法(例如 GAS),则等效为“mov src dest”。 (3认同)
  • 将**汇编语法**更改为“MOV EDX, EBX + 8*EAX + 4”(不带括号)不会改变任何事情。这只是完全相同的操作码和操作数的不同拼写。传统的反汇编器会查看从此语法组装的指令并将其打印为“LEA [EDX, EBX + 8*EAX + 4]”。然而,语法“MOV EDX, EBX + 8*EAX + 4”看起来很愚蠢,因为它正在执行算术运算,但被称为“移动”。“LEA”本身就很好:仅执行 MOV 的地址计算,然后生成结果。 (3认同)
  • @Martin LEA中使用的语法必须使用括号,因为LEA建立在寻址模式语法上。给定“ MOV dest,every”,我们可以将“ MOV”替换为“ LEA”,使“ LEA dest,every”,而无需更改“ whatever”的语法。这很重要,因为“随便什么”都有一种表示某种寻址模式的语法。我们希望从该寻址模式中弹出的地址,而不是被寻址的对象。**并非所有寻址方式都带有方括号!**例如,“ MOV EAX,globalvar”转到“ LEA EAX,globalvar”。 (2认同)
  • @Martin这个难题的缺失部分是LEA适用于任何寻址模式,包括那些没有括号语法的模式.它与该语法正交,我相信它也在指令编码级别:LEA中的"mod/rm"位与MOV和其他指令中的位相同.我们可以创建一个类似于"MOV dest,&source"的语法,其中`&`将它转换为`LEA`(并且仅在`MOV`中可用,而不是在`ADD`或其他什么).半页的Unix脚本可以提供此语法作为预处理器.:) (2认同)

Fra*_*ger 534

从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).

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

  • @AbidRahmanK的一些例子:`LEA EAX,[EAX + EBX + 1234567]`计算`EAX`,`EBX`和`1234567`(这是三个操作数)的总和.`LEA EAX,[EBX + ECX]`计算`EBX + ECX` _without_覆盖结果.第三件事`LEA`用于(未列出的Frank)是_乘以constant_(乘两个,三个,五个或九个),如果你使用它就像`LEA EAX,[EBX + N*EBX]`(`N `可以是1,2,4,8).其他用例在循环中很方便:`LEA EAX,[EAX + 1]`和`INC EAX`之间的区别在于后者改变了'EFLAGS`但前者没有改变; 这保留了`CMP`状态 (41认同)
  • @ ripDaddy69是的,有点 - 如果通过"加载"你的意思是"执行地址计算/指针算术".它确实没有访问memory_(即没有"取消引用"指针,因为它在C编程术语中被调用). (5认同)
  • +1:这明确了什么样的'技巧'`LEA`可用于......(参见"LEA(加载有效地址)经常被用作"技巧"进行某些计算"在IJ肯尼迪的流行答案中) (2认同)
  • 快速的2个操作数LEA和缓慢的3个操作数LEA之间有很大的区别。英特尔优化手册说,快速路径LEA是单周期,而慢速路径LEA需要三个周期。此外,在Skylake上,有两个快速路径功能单元(端口1和5),而只有一个慢路径功能单元(端口1)。手册中的汇编/编译器编码规则33甚至警告不要使用3个操作数LEA。 (2认同)
  • 我觉得这个例子缺乏确切的数字,所以在这里。假设 EBX=5,ECX=3。那么在`LEA EAX之后,[EBX + ECX]` EAX将包含8。在`LEA EAX之后,[EBX + ECX + 2]` EAX将包含10。 (2认同)
  • “(N 可以是 1、2、4、8)。” 如英特尔® 64 和 IA-32 架构软件开发人员手册第 1 卷 3.7.5 指定偏移量中所述 (2认同)

小智 105

LEA指令的另一个重要特征是它不会改变条件代码,例如CFZF,在通过算术指令计算地址时,ADD或者MUL是.此功能降低了指令之间的依赖性级别,从而为编译器或硬件调度程序进一步优化留出了空间.

  • 是的,`lea` 有时对于编译器(或人类编码器)进行数学运算而不破坏标志结果很有用。但是 `lea` 并不比 `add` 快。大多数 x86 指令写入标志。高性能 x86 实现必须重命名 EFLAGS 或以其他方式避免 [写后写风险](https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Data_hazards) 才能使正常代码快速运行,因此说明因此,避免标志写入并不是更好。(*部分*标志的东西可能会产生问题,请参阅[INC指令与ADD 1:是否重要?](//stackoverflow.com/q/36510095)) (2认同)
  • @PeterCordes:讨厌在这里提出这个问题,但是-我是否一个人在想这个新的[x86-lea]标签是多余且不必要的? (2认同)
  • @MichaelPetch:是的,我认为这太具体了。似乎使不懂机器语言的初学者感到困惑,并且所有内容(包括指针)都只是位/字节/整数,因此,关于它的大量投票有很多问题。但是带有标签意味着未来的问题有无限的余地,而实际上总共有2或3个不仅仅是重复的。(这是什么?如何将其用于整数乘法?以及它如何在AGU与ALU之间内部运行以及具有什么延迟/吞吐量。这也许是“预期的”目的) (2认同)

hda*_*nte 88

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

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b
Run Code Online (Sandbox Code Playgroud)

只是它的名字对于shift + add操作极其愚蠢.原因已在最高评级的答案中解释(即它被设计为直接映射高级内存引用).

  • @BenVoigt我曾经说过,因为我是一个老家伙:-)传统上,x86 CPU确实使用了寻址单元,同意.但是这些日子的"分离"变得非常模糊.一些CPU根本不再具有_dedicated_AGU,其他CPU选择不在AGU上执行"LEA",而是在普通的整数ALU上执行.人们必须非常仔细地阅读CPU规格,以找出"运行的地方"...... (29认同)
  • 并且算术由地址计算硬件执行. (7认同)
  • 不,这个名字并不愚蠢.`LEA`为您提供任何与存储器相关的寻址模式产生的地址.这不是转移和添加操作. (3认同)
  • @FrankH.:乱序 CPU 通常在 ALU 上运行 LEA,而一些有序 CPU(如 Atom)有时会在 AGU 上运行它(因为它们不能忙于处理内存访问)。 (2认同)
  • FWIW当前的x86 CPU很少(如果有的话)在AGU上执行操作.大多数或全部只使用ALU,就像任何其他算术运算一样. (2认同)
  • 如果您认为创建指令集架构是为了将架构细节与上层隔离,那么理解这个问题就非常简单。嵌入 LEA 在 AGU 中执行的事实显然与 ISA 无关,并且会产生对架构的错误解释以及对指令本身的怀疑。 (2认同)
  • @Kaz:是的,这是一条有用的指令,并且通过公开地址生成硬件在早期 CPU 中实现的成本很低。有些甚至仍然以这种方式工作,例如用于复杂 LEA 的 K8/K10。(正如 [Andy Glew 解释的](/sf/ask/55425891/),一些 CPU 的 ALU 只能处理 2输入,因此某些 LEA 必须在 AGU 上运行)。但是*它有用的原因是它是一个 shift+add 指令,并且它不会破坏它的任何输入寄存器*。执行细节仅在针对特定 uarch 进行调优时才重要。 (2认同)

GJ.*_*GJ. 74

也许只是关于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
Run Code Online (Sandbox Code Playgroud)

  • @AbidRahmanK尽管intel asm语法使它看起来像一个乘法,但lea指令只能编码移位操作.操作码有2位来描述移位,因此您只能乘以1,2,4或8. (47认同)
  • +1的技巧.但我想问一个问题(可能是愚蠢的),为什么不直接乘以三个像这样的'LEA EAX,[EAX*3]`? (12认同)
  • @Abid Rahman K:没有指令unde x86 CPU指令集. (12认同)
  • @GJ.虽然没有这样的编码,但是一些汇编程序接受这个作为捷径,例如fasm.因此,例如`lea eax,[eax*3]`将转换为相当于`lea eax,[eax + eax*2]`. (7认同)
  • @Koray Tugay:您可以像`shl`instruction一样使用shift左移来将寄存器乘以2,4,8,16 ......它更快更短.但是为了乘以不同于2的幂的数字,我们通常使用更多自命不凡和更慢的"mul"指令. (6认同)
  • @Koray Tugay:是的,你可以,比如:`lea eax,[eax*8]` (2认同)
  • @GJ。为什么你在答案中给出了 3 5 和 9 的例子? (2认同)
  • @GJ.:通常你使用`imul ecx, ebx, 12345`。如果您想要完整乘法的高半结果,则仅使用 `mul`。值得使用 2 个 `lea` 指令来替换乘法,但在现代 CPU 上不是 3。Intel CPU 具有 1c 延迟 LEA,包含 1 或 2 个组件,包括基数 + 缩放索引,因此您可以执行 `lea eax,[ eax+eax*4]` / `lea eax, [edx + eax*2]` 作为 [例如 `atoi` 的 `tot = tot*10 + digit` 循环的一部分](https://stackoverflow. com/questions/19309749/nasm-assembly-convert-input-to-integer/49548057#49548057)。这是 2 个周期的延迟,而 `imul eax,10` / `add eax,edx` 的延迟为 4 (2认同)

Meh*_*ari 57

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

lea ebx, [ebx+eax*8]
Run Code Online (Sandbox Code Playgroud)

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


Dav*_*zer 22

使用LEAa 的最大原因MOV是,如果需要对用于计算地址的寄存器执行算术运算.实际上,您可以有效地对几个寄存器中的指针算术进行有效的"免费"操作.

令人困惑的是,你通常会写一个LEA像a一样MOV但你实际上并没有取消引用内存.换一种说法:

MOV EAX, [ESP+4]

这将移动ESP+4指向的内容EAX.

LEA EAX, [EBX*8]

这会将有效地址移动EBX * 8到EAX中,而不是在该位置找到的地址.正如您所看到的,也可以将因子乘以2(缩放),而a MOV则限制为加/减.

  • @ q4w56这就是答案之一,即“这就是您的操作方式”。我相信,这是人们很难弄清“ LEA”的作用的原因之一。 (3认同)
  • 为什么语法在不进行内存寻址时使用方括号? (2认同)
  • @ q4w56:这是使用内存操作数语法和机器代码编码的shift + add指令。在某些CPU上,它甚至可能使用AGU硬件,但这是一个历史细节。仍然相关的事实是,已经有解码器硬件可用于解码这种shift + add,而LEA让我们将其用于算术运算而不是存储器寻址。(或者用于地址计算(如果一个输入实际上是一个指针))。 (2认同)

sup*_*cat 18

8086具有大型指令系列,其接受寄存器操作数和有效地址,执行一些计算以计算该有效地址的偏移部分,并执行涉及由计算地址引用的寄存器和存储器的一些操作.除了跳过实际的内存操作之外,让该系列中的一个指令表现如上所述是相当简单的.这个,说明:

mov ax,[bx+si+5]
lea ax,[bx+si+5]
Run Code Online (Sandbox Code Playgroud)

内部几乎完全相同.差异是跳过的步骤.两条指令的工作方式如下:

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
Run Code Online (Sandbox Code Playgroud)

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

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"
Run Code Online (Sandbox Code Playgroud)

如果有人想使用像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
Run Code Online (Sandbox Code Playgroud)

比以下更方便:

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
Run Code Online (Sandbox Code Playgroud)

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


Tho*_*son 11

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

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

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

  • 您链接的文章(正确)显示 LEA 与整数 ALU(加/子/布尔)以及 Haswell 中的整数 MUL 单元位于同一端口。(以及矢量 ALU,包括 FP ADD/MUL/FMA)。仅简单的 LEA 单元位于端口 5 上,它还运行 ADD/SUB/其他内容,以及向量洗牌和其他内容。我没有投反对票的唯一原因是您指出了 RIP 相关 LEA 的使用(仅适用于 x86-64)。 (3认同)
  • "单独的LEA ALU"部分基本上是不真实的.现代CPU在执行其他算术指令的一个或多个相同的ALU上执行"lea"(但通常比其他算术更少).例如,提到的Haswell CPU可以在_four different_ ALU上执行`add`或`sub`或大多数其他基本算术运算,但只能在一个(复杂的`lea`)或两个(简单的`lea`)上执行`lea` .更重要的是,那两个具有"lea"功能的ALU只是可以执行其他指令的四个中的两个,因此没有所声称的并行优势. (2认同)

Jae*_*Lee 8

似乎很多答案已经完成,我想再添加一个示例代码,以显示 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
          );
}
Run Code Online (Sandbox Code Playgroud)

执行结果

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
Run Code Online (Sandbox Code Playgroud)


小智 7

LEA指令可用于避免CPU对有效地址进行耗时的计算.如果重复使用地址,则将其存储在寄存器中更有效,而不是每次使用时计算有效地址.

  • @Kaz - 我认为你错过了我的观点(或者我错过了OP的观点).我的理解是OP说如果你要在一系列指令中反复使用`[esi + 4200]`之类的东西,那么最好先将有效地址加载到寄存器中并使用它.例如,而不是写`add eax,[esi + 4200]; 添加ebx,[esi + 4200]; 添加ecx,[esi + 4200]`,你应该更喜欢`lea edi,[esi + 4200]; 添加eax,[edi]; 添加ebx,[edi]; 添加ecx,[edi]`,这很少快.至少这是对这个答案的简单解释. (2认同)

Kaz*_*Kaz 7

LEA(加载有效地址)指令是一种获取由任何Intel处理器的存储器寻址模式产生的地址的方法.

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

MOV EAX, <MEM-OPERAND>
Run Code Online (Sandbox Code Playgroud)

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

如果我们替换了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.
Run Code Online (Sandbox Code Playgroud)

这是有效的; 我们可以在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
Run Code Online (Sandbox Code Playgroud)

这里,没有添加缩放值,也没有偏移量.零被移入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
Run Code Online (Sandbox Code Playgroud)

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

当然,这种LEA编码比将立即零转换为EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax
Run Code Online (Sandbox Code Playgroud)

没有理由LEA排除这种可能性,只是因为有一个较短的选择; 它只是以正交方式与可用的寻址模式相结合.


Max*_*tin 7

LEA vs MOV(回复原问题)

\n

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

\n

在 8086 及更高版本中,LEA只需将最多两个源寄存器和一个立即值设置到目标寄存器。例如,将plus加 3 的lea bp, [bx+si+3]和设置到 bp 寄存器。使用 无法实现将结果保存到寄存器的计算。bxsiMOV

\n

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

\n

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

\n

与 相比LEA,有指令LDSLES,相反,它们将值从内存加载到一对寄存器:一个段寄存器(DSES)和一个通用寄存器。还有其他寄存器的版本:分别为LFSLGSLSSFS和段寄存器(在 80386 中引入)。GSSS

\n

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

\n

这些是 16 位模式的方便指令,无论是 16 位实模式还是 16 位保护模式。

\n

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

\n

在 64 位模式下,这些指令不被执行。它们的操作码会产生访问冲突中断(异常)。自从英特尔实施 VEX -“矢量扩展 - (AVX) 以来,英特尔采用了 和 的操作码LDSLES开始将它们用于 VEX 前缀。正如 Peter Cordes 指出的那样,这就是为什么只有 x/ymm0..7 在 32 位中可以访问模式(引用):“VEX 前缀经过精心设计,仅与 32 位模式下的 LDS 和 LES 的无效编码重叠,其中 R\xcc\x85 X\xcc\x85 B\xcc\x85 均为 1。这就是为什么VEX 前缀中的一些位被反转”。

\n

  • LES/LDS/...在 32 位模式下无用只是按照惯例;操作系统选择使用平面内存模型。与 64 位模式不同,硬件在 32 位模式下支持非零段基。因此,实际上对于普通操作系统来说确实如此,但对于一般的 x86 来说不一定如此。因此,也许“因为操作系统将所有段寄存器设置为相同”或其他什么,暗示这是一种选择,而不是要求,32 位模式以这种方式工作,但仍然不需要在上面花费很多文字并分散您的注意力。观点。 (2认同)

小智 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;
}
Run Code Online (Sandbox Code Playgroud)

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


the*_*ant 5

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

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


归档时间:

查看次数:

542630 次

最近记录:

6 年,1 月 前