【问题标题】:What is the difference between MOV and LEA?MOV和LEA有什么区别?
【发布时间】:2010-12-14 13:25:23
【问题描述】:

我想知道这些指令有什么区别:

MOV AX, [TABLE-ADDR]

LEA AX, [TABLE-ADDR]

【问题讨论】:

标签: assembly x86 instruction-set


【解决方案1】:
  • LEA 表示加载有效地址
  • MOV 表示加载值

简而言之,LEA 加载指向您正在寻址的项目的指针,而 MOV 加载该地址处的实际值。

LEA 的目的是允许人们执行非平凡的地址计算并存储结果[供以后使用]

LEA ax, [BP+SI+5] ; Compute address of value

MOV ax, [BP+SI+5] ; Load value at that address

在只涉及常量的情况下,MOV(通过汇编程序的常量计算)有时会与LEA 的最简单使用情况重叠。如果您有多个基地址等的多部分计算,它很有用。

【讨论】:

  • +1 感谢您的清晰解释,帮助我answer 另一个问题。
  • 让我感到困惑的是 lea 在名称中包含“加载”,人们说它将计算的地址“加载”到寄存器中,因为计算内存位置的所有输入都是立即值或寄存器. AFAICT lea 只执行计算,它不加载任何东西,加载意味着接触内存?
  • @josephGarvin IIRC 术语 fetch 将应用于该方面;加载就是你如何用从头开始的东西替换寄存器中的值。例如LAHF 是:将 FLAGS 加载到 AH 寄存器中。在 CLR 的 CIL(它是一个更高级别的基于堆栈的抽象机,术语 load 是指将一个值放到概念堆栈上,通常是 l... 和 s。 ..等价的反之亦然)。这些注释:cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html) 表明确实存在适用于您的区别的架构。
  • 这一切让我想起了slideshare.net/pirhilton/… ;)
【解决方案2】:

在 NASM 语法中:

mov eax, var       == lea eax, [var]   ; i.e. mov r32, imm32
lea eax, [var+16]  == mov eax, var+16
lea eax, [eax*4]   == shl eax, 2        ; but without setting flags

在 MASM 语法中,使用 OFFSET var 获取 mov-immediate 而不是加载。

【讨论】:

  • 仅在 NASM 语法中。在 MASM 语法中,mov eax, var 是一个负载,与mov eax, [var] 相同,您必须使用mov eax, OFFSET var 才能将标签用作立即常量。
  • 清晰、简单,并演示了我试图确认的内容。谢谢。
  • 请注意,在所有这些示例中,lea 是更糟糕的选择,除了用于 RIP 相对寻址的 64 位模式。 mov r32, imm32 在更多端口上运行。 lea eax, [edx*4] 是一种复制和移位,否则无法在一条指令中完成,但在同一个寄存器中,LEA 只需要更多字节来编码,因为[eax*4] 需要disp32=0。 (不过,它运行在与班次不同的端口上。)请参阅 agner.org/optimizestackoverflow.com/tags/x86/info
【解决方案3】:

MOV reg,addr 指令将地址addr 处的变量读入寄存器reg。指令 LEA reg,addr 表示将地址(不是该地址存储的变量)读入寄存器 reg。

MOV 指令的另一种形式是 MOV reg,immdata,这意味着将立即数(即常量) immdata 读入寄存器 reg。请注意,如果 LEA reg,addr 中的 addr 只是一个常数(即固定偏移量),则该 LEA 指令本质上与加载与立即数据相同的常数的等效 MOV reg,immdata 指令完全相同。

【讨论】:

    【解决方案4】:

    之前的答案都没有完全解决我自己的困惑,所以我想添加自己的答案。

    我缺少的是lea 操作处理括号的使用与mov 不同。

    想想 C。假设我有一个 long 数组,我称之为 array。现在表达式array[i] 执行解引用,从内存地址array + i * sizeof(long) [1] 处加载值。

    另一方面,考虑表达式&array[i]。这仍然包含子表达式array[i],但不执行解引用! array[i] 的含义已经改变。它不再意味着执行服从,而是充当一种规范,告诉&我们正在寻找什么内存地址。如果您愿意,您也可以将& 视为“取消”取消引用。

    因为这两个用例在很多方面都很相似,所以它们共享语法 array[i],但 & 的存在与否会改变该语法的解释方式。没有&,它是一个取消引用,实际上是从数组中读取的。对于&,它不是。 array + i * sizeof(long) 的值仍在计算中,但并未取消引用。

    movlea 的情况非常相似。使用mov,会发生lea 不会发生的取消引用。尽管在两者中都使用了括号。例如,movq (%r8), %r9leaq (%r8), %r9。对于mov,这些括号表示“取消引用”;与lea,他们没有。这类似于array[i] 在没有& 时仅表示“取消引用”。

    举个例子。

    考虑代码

    movq (%rdi, %rsi, 8), %rbp
    

    这会将内存位置%rdi + %rsi * 8 的值加载到寄存器%rbp 中。即:获取寄存器%rdi中的值和寄存器%rsi中的值。将后者乘以 8,然后将其与前者相加。 找到该位置的值并将其放入寄存器%rbp

    这段代码对应于C行x = array[i];,其中array变成%rdii变成%rsix变成%rbp8 是数组中包含的数据类型的长度。

    现在考虑使用lea的类似代码:

    leaq (%rdi, %rsi, 8), %rbp
    

    正如movq 的使用对应于解引用,leaq 的使用在这里对应 解引用。这一行汇编对应于C行x = &array[i];。回想一下&array[i] 的含义从取消引用更改为简单地指定位置。同样,leaq 的使用将 (%rdi, %rsi, 8) 的含义从取消引用更改为指定位置。

    这行代码的语义如下:获取寄存器%rdi中的值和寄存器%rsi中的值。将后者乘以 8,然后将其与前者相加。将此值放入寄存器%rbp。不涉及内存加载,仅涉及算术运算 [2]。

    请注意,我对leaqmovq 的描述之间的唯一区别是movq 进行了取消引用,而leaq 没有。其实写leaq的描述,我基本上是复制+粘贴movq的描述,然后去掉“在这个位置找值”。

    总结一下:movqleaq 比较棘手,因为它们处理括号的使用方式不同,如 (%rsi)(%rdi, %rsi, 8)。在movq(以及除lea 之外的所有其他指令)中,这些括号表示真正的取消引用,而在leaq 中,它们不是并且纯粹是方便的语法。


    [1] 我说过当arraylong 的数组时,表达式array[i] 会从地址array + i * sizeof(long) 加载值。这是真的,但有一个微妙之处需要解决。如果我写 C 代码

    long x = array[5];
    

    和打字一样

    long x = *(array + 5 * sizeof(long));
    

    似乎应该根据我之前的说法,其实不是。

    发生的事情是 C 指针加法有一个技巧。假设我有一个指针p 指向T 类型的值。表达式p + i 确实不是的意思是“p 的位置加上i 字节”。相反,表达式p + i 实际上 的意思是“p 的位置加上i * sizeof(T) 字节”。

    这样做的方便之处在于,要获得“下一个值”,我们只需编写 p + 1 而不是 p + 1 * sizeof(T)

    这意味着C代码long x = array[5];实际上等价于

    long x = *(array + 5)
    

    因为 C 会自动将 5 乘以 sizeof(long)

    那么在这个 StackOverflow 问题的上下文中,这一切有什么关系?这意味着当我说“地址array + i * sizeof(long)”时,我确实不是意味着将“array + i * sizeof(long)”解释为C 表达式。我自己乘以 sizeof(long) 是为了让我的答案更明确,但请理解,因此,这个表达式不应被解读为 C。就像使用 C 语法的普通数学一样。

    [2] 旁注:因为所有lea 所做的都是算术运算,所以它的参数实际上不必引用有效地址。出于这个原因,它通常用于对可能不打算取消引用的值执行纯算术运算。例如,cc-O2 优化转换

    long f(long x) {
      return x * 5;
    }
    

    进入以下(不相关的行已删除):

    f:
      leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
      ret
    

    【讨论】:

    • 是的,很好的解释,比其他答案更详细,是的 C 的 & 运算符是一个很好的类比。或许值得指出的是,LEA 是一种特殊情况,而 MOV 就像其他可以获取内存或寄存器操作数的指令一样。例如add (%rdi), %eax 只是使用寻址模式来寻址内存,与 MOV 相同。也相关:Using LEA on values that aren't addresses / pointers? 进一步解释:LEA 是您如何使用 CPU 对地址数学的硬件支持来进行任意计算。
    • "get the value at %rdi" -- 这个措辞很奇怪。您的意思是应该使用寄存器中的值 rdi。您对“at”的使用似乎意味着没有内存引用。
    • @PeterCordes 谢谢!我已经在答案中添加了关于它是特例的观点。
    • @ecm 好点;我没有注意到这一点。我现在改了,谢谢! :)
    • 最后一个技巧真是太棒了。编译器在提高 exe 效率方面确实做得很好。
    【解决方案5】:

    如果只指定文字,则没有区别。不过,LEA 具有更多功能,您可以在此处了解它们:

    http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136

    【讨论】:

    • 我猜,除了在 GNU 汇编器中,当涉及到 .bss 段中的标签时,它不是真的? AFAIR 你真的不能leal TextLabel, LabelFromBssSegment 当你得到smth。像.bss .lcomm LabelFromBssSegment, 4,你必须movl $TextLabel, LabelFromBssSegment,不是吗?
    • @JSmyth:那只是因为lea 需要一个寄存器目标,但mov 可以有一个imm32 源和一个内存目标。这个限制当然不是特定于 GNU 汇编器的。
    • 另外,这个答案基本上是错误的,因为问题是询问MOV AX, [TABLE-ADDR],这是一个负担。所以有很大的不同。等效指令为mov ax, OFFSET table_addr
    • 链接已失效。
    【解决方案6】:

    这取决于使用的汇编程序,因为

    mov ax,table_addr
    

    在 MASM 中作为

    mov ax,word ptr[table_addr]
    

    所以它从table_addr 加载第一个字节,而不是table_addr 的偏移量。你应该改用

    mov ax,offset table_addr
    

    lea ax,table_addr
    

    效果相同。

    lea 版本也可以在 table_addr 是局部变量时正常工作,例如

    some_procedure proc
    
    local table_addr[64]:word
    
    lea ax,table_addr
    

    【讨论】:

    • 非常感谢,只是我不能将多个标记为答案:(
    • x86 指令 MOV 和 LEA 之间的区别绝对不取决于汇编程序。
    【解决方案7】:

    如其他答案所述:

    • MOV 将在括号内的地址处获取 数据,并将该 数据 放入目标操作数中。
    • LEA 将执行括号内地址的计算,并将 计算的地址 放入目标操作数。这发生在没有真正进入内存并获取数据的情况下。 LEA所做的工作是计算“有效地址”。

    由于内存可以通过多种不同的方式寻址(参见下面的示例),LEA 有时用于将寄存器相加或相乘,而无需使用显式的 ADDMUL 指令(或等效指令)。

    由于每个人都在展示 Intel 语法的示例,以下是一些 AT&T 语法:

    MOVL 16(%ebp), %eax       /* put long  at  ebp+16  into eax */
    LEAL 16(%ebp), %eax       /* add 16 to ebp and store in eax */
    
    MOVQ (%rdx,%rcx,8), %rax  /* put qword at  rcx*8 + rdx  into rax */
    LEAQ (%rdx,%rcx,8), %rax  /* put value of "rcx*8 + rdx" into rax */
    
    MOVW 5(%bp,%si), %ax      /* put word  at  si + bp + 5  into ax */
    LEAW 5(%bp,%si), %ax      /* put value of "si + bp + 5" into ax */
    
    MOVQ 16(%rip), %rax       /* put qword at rip + 16 into rax                 */
    LEAQ 16(%rip), %rax       /* add 16 to instruction pointer and store in rax */
    
    MOVL label(,1), %eax      /* put long at label into eax            */
    LEAL label(,1), %eax      /* put the address of the label into eax */
    

    【讨论】:

    • 对于绝对的[disp32] 寻址模式,您永远不需要lea label, %eax。请改用mov $label, %eax。是的,它有效,但效率较低(更大的机器代码并在更少的执行单元上运行)。既然你提到了 AT&T,Using LEA on values that aren't addresses / pointers? 使用 AT&T,我的回答还有其他一些 AT&T 示例。
    【解决方案8】:

    基本上……“在计算之后移入 REG……” 其他用途似乎也不错:)

    如果你忘记了这个值是一个指针 您可以将其用于代码优化/最小化......无论怎样......

    MOV EBX , 1
    MOV ECX , 2
    
    ;//with 1 instruction you got result of 2 registers in 3rd one ...
    LEA EAX , [EBX+ECX+5]
    

    EAX = 8

    原来是这样的:

    MOV EAX, EBX
    ADD EAX, ECX
    ADD EAX, 5
    

    【讨论】:

    【解决方案9】:

    让我们通过一个例子来理解这一点。

    mov eax, [ebx] 和

    lea eax, [ebx] 假设 ebx 中的值为 0x400000。然后 mov 将转到地址 0x400000 并将它们的 4 字节数据复制到 eax 寄存器。而 lea 将地址 0x400000 复制到 eax。所以,每条指令执行后,每种情况下eax的值都会是(假设在内存0x400000包含的是30)。

    eax = 30(在 mov 的情况下) eax = 0x400000(如果是 lea) 对于定义,mov将数据从rm32复制到目标(mov dest rm32),lea(加载有效地址)将地址复制到目标(mov dest rm32)。

    【讨论】:

      【解决方案10】:

      MOV 可以做与 LEA [label] 相同的事情,但 MOV 指令包含指令本身内部的有效地址作为立即常数(由汇编程序预先计算)。 LEA 在指令执行过程中使用 PC-relative 计算有效地址。

      【讨论】:

      • 这仅适用于 64 位模式(PC 相对寻址是新的);在其他模式下lea [label 与更紧凑的mov 相比,浪费字节是毫无意义的,因此您应该指定您正在谈论的条件。此外,对于某些汇编程序,[label] 不是 RIP 相对寻址模式的正确语法。但是,是的,这是准确的。 How to load address of function or label into register in GNU Assembler 解释更详细。
      【解决方案11】:

      LEA(加载有效地址)是移位加指令。它被添加到 8086 是因为有硬件来解码和计算寻址模式。

      【讨论】:

        【解决方案12】:

        区别很微妙但很重要。 MOV 指令是一个“MOVe”,实际上是 TABLE-ADDR 标签所代表的地址的副本。 LEA 指令是“加载有效地址”,它是一条间接指令,这意味着 TABLE-ADDR 指向要加载的地址所在的内存位置。

        有效地使用 LEA 相当于在 C 等语言中使用指针,因为它是一个强大的指令。

        【讨论】:

        • 我认为这个答案充其量是令人困惑的。 “LEA 指令是一条‘加载有效地址’,它是一条间接指令,这意味着 TABLE-ADDR 指向找到要加载的地址的内存位置。”实际上 LEA 将加载地址,而不是地址的内容。我认为实际上需要向提问者保证 MOV 和 LEA 可以重叠,并且在某些情况下做完全相同的事情
        猜你喜欢
        • 1970-01-01
        • 2016-05-30
        • 2017-05-05
        • 2011-02-17
        • 1970-01-01
        • 2019-04-15
        • 1970-01-01
        相关资源
        最近更新 更多