我想知道这些指令之间的区别是:
MOV AX, [TABLE-ADDR]
和
LEA AX, [TABLE-ADDR]
我想知道这些指令之间的区别是:
MOV AX, [TABLE-ADDR]
和
LEA AX, [TABLE-ADDR]
Answers:
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
。如果您要进行包含多个基地址等的多部分计算,则它很有用。
LAHF
:将FLAGS加载到AH寄存器中。在CLR的CIL(这是一个基于更高级别堆栈的抽象机中,术语“ 负载”是指将一个值放到概念堆栈上,通常是l
...,而s
...等效项是相反的)。这些注释:cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html)建议确实存在确实适用您的区别的体系结构。
使用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而不是加载。
mov eax, var
是与相同的负载,mov eax, [var]
并且您必须使用mov eax, OFFSET var
来将标签用作立即常量。
lea
除了在64位模式下相对RIP寻址之外,是最差的选择。 mov r32, imm32
在更多端口上运行。 lea eax, [edx*4]
是一个复制移位操作,否则不能在一条指令中完成,但是在同一寄存器中,LEA只需要占用更多字节即可进行编码,因为[eax*4]
需要使用disp32=0
。(不过,它运行在与shift不同的端口上。)请参阅agner.org/optimize和stackoverflow.com/tags/x86/info。
如果仅指定文字,则没有区别。但是,LEA具有更多功能,您可以在此处阅读有关它们的信息:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
leal TextLabel, LabelFromBssSegment
时候,你真的无法做到。就像.bss .lcomm LabelFromBssSegment, 4
,您将不得不movl $TextLabel, LabelFromBssSegment
,不是吗?
lea
需要一个寄存器目标,但是mov
可以有一个imm32
源和一个存储器目标。当然,此限制并非特定于GNU汇编程序。
MOV AX, [TABLE-ADDR]
,这是一个负担。因此有一个很大的不同。等效的指示是mov ax, OFFSET table_addr
这取决于使用的汇编程序,因为
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
以前的答案都没有完全弄清我自己的困惑,因此我想添加自己的答案。
我所缺少的是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), %r9
和leaq (%r8), %r9
。使用mov
,这些括号表示“取消引用”;与lea
,他们没有。这类似于array[i]
没有时仅表示“取消引用”的意思&
。
有一个例子。
考虑代码
movq (%rdi, %rsi, 8), %rbp
这会将存储位置的值加载%rdi + %rsi * 8
到寄存器中%rbp
。即:获取寄存器中%rdi
的值和寄存器中的值%rsi
。将后者乘以8,然后将其添加到前者。在此位置查找值并将其放入寄存器%rbp
。
该代码对应于C线x = array[i];
,在那里array
变%rdi
和i
变%rsi
和x
变%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
,然后删除了“在此位置查找值”。
总结一下:movq
vs. 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指针加法了。假设我有一个指向p
type值的指针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
&
运算符很不错。也许值得指出的是,LEA是特例,而MOV就像其他每个可以占用内存或寄存器操作数的指令一样。例如,add (%rdi), %eax
仅使用寻址模式来寻址存储器,与MOV相同。还相关:在不是地址/指针的值上使用LEA?进一步说明:LEA是如何使用CPU的硬件支持对地址数学进行任意计算。
%rdi
”-这是奇怪的措辞。您的意思是应该使用寄存器 中的值rdi
。您对“ at”的使用似乎意味着在没有引用的情况下进行内存取消引用。
%rdi
或”值“ 中 %rdi
”。您的“寄存器中的值%rdi
”很长,但是还不错,也许可以帮助那些难以理解寄存器与内存的人。
基本上……“在计算完之后移入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
如其他答案所述:
MOV
将抢在数据括号内的地址和位置数据到目标操作数。LEA
将在括号内执行地址的计算,并将计算出的地址放入目标操作数。发生这种情况时并没有实际访问内存并获取数据。所做的工作LEA
是在“有效地址”的计算中。因为可以用几种不同的方式来寻址内存(请参见下面的示例),LEA
所以有时可将其用于将寄存器加或乘在一起而不使用显式ADD
或MUL
指令(或等效方法)。
由于每个人都以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示例。
MOV可以执行与LEA [label]相同的操作,但是MOV指令包含指令内部的有效地址作为立即数常量(由汇编程序预先计算)。LEA使用PC相对值来计算指令执行期间的有效地址。
lea [label
下字节的浪费是毫无意义的mov
,因此您应该指定要讨论的条件。同样,对于某些汇编器[label]
来说,RIP相对寻址模式的语法也不正确。但是,是的,这是正确的。 如何将功能或标签的地址加载到GNU汇编器中的寄存器中,将更加详细地说明。
差异是微妙的,但很重要。MOV指令实际上是TABLE-ADDR标签所代表的地址的“ MOVe”副本。LEA指令是“加载有效地址”,它是一个间接指令,这意味着TABLE-ADDR指向找到要加载地址的内存位置。
有效地使用LEA等效于使用诸如C之类的语言来使用指针,因此,它是功能强大的指令。