MOV和LEA有什么区别?


138

我想知道这些指令之间的区别是:

MOV AX, [TABLE-ADDR]

LEA AX, [TABLE-ADDR]


8
谢谢尼克。首先,通过查看该链接,我找不到该问题的答案。在这里,我一直在寻找特定的信息,您提供的链接中的讨论本质上更为笼统。
naveen

3
我很久以前就投票赞成@Nick的dup,但是现在才进行。经过反思,我太匆忙了,现在带着naveen,a)另一个问题没有回答“有什么区别”,b)这是一个有用的问题。抱歉为我的错误感到厌倦-如果我只能撤消vtc ...
鲁本·巴特林克


相关:在不是地址/指针的值上使用LEA吗?讨论了LEA用于任意数学的其他用途。
彼得·科德斯

Answers:


168
  • 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。如果您要进行包含多个基地址等的多部分计算,则它很有用。


6
+1感谢您的清晰解释,帮助我回答了另一个问题。
legends2k 2014年

这使我感到困惑,因为lea的名称中包含“ load”,人们说它将“计算的”地址“加载”到寄存器中,因为所有用于计算内存位置的输入都是立即数或寄存器。AFAICT lea仅执行计算,不加载任何内容,其中加载意味着接触内存?
Joseph Garvin

2
@josephGarvin IIRC,“提取”一词将应用于该方面;加载就是您从头开始用某些东西替换寄存器中的值的方法。例如LAHF:将FLAGS加载到AH寄存器中。在CLR的CIL(这是一个基于更高级别堆栈的抽象机中,术语“ 负载”是指将一个值放到概念堆栈上,通常是l...,而s...等效项是相反的)。这些注释:cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html)建议确实存在确实适用您的区别的体系结构。
鲁宾·巴特林克


45

使用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而不是加载。


3
仅在NASM语法中。在MASM语法中,mov eax, var是与相同的负载,mov eax, [var]并且您必须使用mov eax, OFFSET var来将标签用作立即常量。
彼得·科德斯

1
清晰,简单,并演示了我要确认的内容。谢谢。
JayArby

1
请注意,在所有这些示例中,lea除了在64位模式下相对RIP寻址之外,是最差的选择。 mov r32, imm32在更多端口上运行。 lea eax, [edx*4]是一个复制移位操作,否则不能在一条指令中完成,但是在同一寄存器中,LEA只需要占用更多字节即可进行编码,因为[eax*4]需要使用disp32=0。(不过,它运行在与shift不同的端口上。)请参阅agner.org/optimizestackoverflow.com/tags/x86/info
彼得·科德斯

29

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

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


11

如果仅指定文字,则没有区别。但是,LEA具有更多功能,您可以在此处阅读有关它们的信息:

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


我猜,除了在GNU汇编器中,.bss段中的标签不是真的吗?没事的leal TextLabel, LabelFromBssSegment时候,你真的无法做到。就像.bss .lcomm LabelFromBssSegment, 4,您将不得不movl $TextLabel, LabelFromBssSegment,不是吗?
JSmyth

@JSmyth:这仅是因为lea需要一个寄存器目标,但是mov可以有一个imm32源和一个存储器目标。当然,此限制并非特定于GNU汇编程序。
彼得·科德斯

1
同样,这个答案基本上是错误的,因为问题在询问MOV AX, [TABLE-ADDR],这是一个负担。因此有一个很大的不同。等效的指示是mov ax, OFFSET table_addr
Peter Cordes

10

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

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

非常感谢,只是我不能将一个以上的答案标记为:(
naveen

5
x86指令MOV和LEA之间的差异绝对不取决于汇编程序。
IJ肯尼迪

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)仍会计算,但不会取消引用。

mov和的情况非常相似lea。使用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%rbp。的8是包含在数组中的数据类型的长度。

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

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

正如使用movq对应于取消引用一样,leaq此处的使用也对应于取消引用。该装配线对应于C线x = &array[i];。回想一下,&array[i]将从取消引用的含义更改为仅指定位置。同样,使用leaq改变(%rdi, %rsi, 8)从取消引用到指定位置的含义。

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

请注意,我对leaq和的描述之间的唯一区别movq是进行movq了取消引用,并且leaq没有。实际上,要编写leaq描述,我基本上复制并粘贴了的描述movq,然后删除了“在此位置查找值”。

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


[1]我说过,当array是的数组时long,表达式array[i]从address加载值array + i * sizeof(long)。的确如此,但是有一个微妙之处应该解决。如果我写C代码

long x = array[5];

这是一样的打字

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

似乎应该基于我之前的陈述,但事实并非如此。

发生了什么事,就是C指针加法了。假设我有一个指向ptype值的指针T。表达p + i确实平均“的位置p加上i字节”。相反,该表达式p + i 实际上表示“ pi * sizeof(T)字节的位置”。

这样做的方便之处在于获得“未来价值”,我们只需要编写p + 1代替p + 1 * sizeof(T)

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

long x = *(array + 5)

因为C会自动繁衍5sizeof(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

1
是的,很好的解释,比其他答案更详细,是的,C的&运算符很不错。也许值得指出的是,LEA是特例,而MOV就像其他每个可以占用内存或寄存器操作数的指令一样。例如,add (%rdi), %eax仅使用寻址模式来寻址存储器,与MOV相同。还相关:在不是地址/指针的值上使用LEA?进一步说明:LEA是如何使用CPU的硬件支持对地址数学进行任意计算。
Peter Cordes

“在……处获得价值%rdi”-这是奇怪的措辞。您的意思是应该使用寄存器 中的值rdi。您对“ at”的使用似乎意味着在没有引用的情况下进行内存取消引用。
ecm

@PeterCordes谢谢!我已经添加了关于它是特殊情况的答案。
Quelklef

1
@ecm好点;我没注意到。我现在已经更改了,谢谢!:)
Quelklef

仅供参考,更短的措辞可解决该问题ECM指出包括:“的值 %rdi或”值“ %rdi ”。您的“寄存器中的值%rdi”很长,但是还不错,也许可以帮助那些难以理解寄存器与内存的人。
Peter Cordes

2

基本上……“在计算完之后移入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

lea是的使用内存操作数机器编码和语法的移位加法指令,因为硬件已经知道如何解码ModR / M + SIB + disp0 / 8/32。
彼得·科德斯

1

如其他答案所述:

  • 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 */

您永远不需要lea label, %eax绝对[disp32]寻址模式。使用mov $label, %eax代替。是的,它可以工作,但是效率较低(机器代码更大,并且在更少的执行单元上运行)。既然您提到了AT&T,那么在不是地址/指针的值上使用LEA吗?使用AT&T,我的回答还有其他一些AT&T示例。
彼得·科德斯

1

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

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) )。


0

LEA(加载有效地址)是移位加法指令。它被添加到8086,因为那里有硬件可以解码和计算地址模式。


0

MOV可以执行与LEA [label]相同的操作,但是MOV指令包含指令内部的有效地址作为立即数常量(由汇编程序预先计算)。LEA使用PC相对值来计算指令执行期间的有效地址。


这仅适用于64位模式(相对于PC的寻址是新的)。与其他模式相比,在其他模式lea [label下字节的浪费是毫无意义的mov,因此您应该指定要讨论的条件。同样,对于某些汇编器[label]来说,RIP相对寻址模式的语法也不正确。但是,是的,这是正确的。 如何将功能或标签的地址加载到GNU汇编器中的寄存器中,将更加详细地说明。
Peter Cordes

-1

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

有效地使用LEA等效于使用诸如C之类的语言来使用指针,因此,它是功能强大的指令。


7
我认为这个答案充其量是令人困惑的。“ LEA指令是一个“加载有效地址”,它是一个间接指令,这意味着TABLE-ADDR指向找到要加载地址的内存位置。” 实际上,LEA会加载地址,而不是地址的内容。我认为实际上需要让提问者放心,在某些情况下,MOV和LEA可以重叠,并且做完全相同的事情
比尔·福斯特
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.