首页 文章

LEA指令的目的是什么?

提问于
浏览
565

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

14 回答

  • 67

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

    struct Point
    {
         int xcoord;
         int ycoord;
    };
    

    现在想象一下如下声明:

    int y = points[i].ycoord;
    

    其中 points[]Point 的数组 . 假设数组的基数已经在 EBX 中,变量 iEAX 中,并且 xcoordycoord 每个都是32位(因此 ycoord 在结构中偏移4个字节),可以将此语句编译为:

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

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

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

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

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

    这将加载 ESI 中的地址 .

  • 485

    来自Abrash的"Zen of Assembly":

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

    并且 LEA 不会改变标志 .

    例子

    • LEA EAX, [ EAX + EBX + 1234567 ] 计算 EAX + EBX + 1234567 (这是三个操作数)

    • LEA EAX, [ EBX + ECX ] 计算 EBX + ECX 而不覆盖结果 .

    • 乘以常数(乘以二,三,五或九),如果你像_814585那样使用它(N可以是1,2,4,8) .

    其他用例在循环中很方便: LEA EAX, [ EAX + 1 ]INC EAX 之间的区别在于后者改变 EFLAGS 但前者没有改变 . 这保留了 CMP 州 .

  • 90

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

  • 76

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

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

    只是它的名字对于移位添加操作来说极其愚蠢 . 其原因已经在最高评级的答案中解释(即它被设计为直接映射高级别的内存参考) .

  • 17

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

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

    lea ebx, [ebx+eax*8]
    

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

  • 7

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

    真正令人困惑的是你通常会像_814606那样写一个 LEA 但你实际上并没有解除引用内存 . 换一种说法:

    MOV EAX, [ESP+4]

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

    LEA EAX, [EBX*8]

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

  • -6

    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
    

    至于为何英特尔认为这一点指令值得包括,I 'm not exactly sure, but the fact that it was cheap to implement would have been a big factor. Another factor would have been the fact that Intel' s汇编程序允许相对于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 . 哎呀 .

  • 52

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

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

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

  • 6

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

  • 10

    这是一个例子 .

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

  • 4

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

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

    MOV EAX, <MEM-OPERAND>
    

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

    如果我们用 LEA 替换 MOV ,那么 <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
    

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

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

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

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

  • 6

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

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

  • 683

    它是因为你编写代码

    mov dx,offset something
    

    你可以简单地写

    lea dx,something
    

相关问题