我想知道这些指令之间的区别:
MOV AX, [TABLE-ADDR]
并且LEA AX, [TABLE-ADDR]
我想知道这些指令之间的区别:
MOV AX, [TABLE-ADDR]
并且LEA AX, [TABLE-ADDR]
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中(它是一种更高级别的基于堆栈的抽象机器),术语“load”指将一个值放入概念堆栈中,并且通常是l
...,而s
...等效操作则执行相反的操作)。 这些笔记( https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html)表明确实存在你所说的区别适用的架构。 - Ruben Bartelinklui $t0, 1
(Load Upper Immediate),它将$a0
设置为1<<16
,尽管在这种情况下,该值作为机器代码的一部分存储在内存中。(或者在像AArch64这样的现代ISA中,以某种方式编码,而不是字面上存在)。是的,当代码有“将1存储在EDI中”的注释时,我感到很烦。 - Peter CordesLAHF
助记符,它会写入一个GP寄存器。(但我总是不得不查一下它是将AH加载到标志中还是从FLAGS中加载,因为FLAGS也是一个寄存器。还有一个SAHF,它可以读取通用寄存器AH。) - Peter Cordesmov 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而不是load。lea
都是更糟糕的选择。 mov r32,imm32
可以在更多端口上运行。 lea eax,[edx * 4]
是一种复制和移位操作,否则无法在一条指令中完成,但在同一个寄存器中,LEA编码需要更多的字节,因为[eax * 4]
需要一个disp32 = 0
。(尽管它运行在不同的端口上)。请参阅http://agner.org/optimize/和https://stackoverflow.com/tags/x86/info。 - Peter Cordes指令MOV reg, addr表示将存储在地址addr中的变量读入寄存器reg。指令LEA reg, addr表示将地址(而不是存储在地址中的变量)读入寄存器reg。
MOV指令的另一种形式是MOV reg,immdata,它表示将立即数据(即常量)immdata读入寄存器reg。请注意,如果LEA reg,addr中的addr只是一个常量(即固定偏移量),那么该LEA指令本质上就相当于等效的MOV reg,immdata指令,其将相同的常量作为立即数据进行加载。
之前的回答并没有完全解决我的困惑,因此我想加上我的看法。
我错过的是 lea
操作对括号的使用与 mov
不同。
想象一下 C 语言。假设我有一个称为 array
的 long
数组。现在表达式 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
中。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
与 leaq
的区别很棘手,因为它们不同地处理括号的使用,例如 (%rsi)
和 (%rdi, %rsi, 8)
。在 movq
(和除 lea
之外的所有指令)中,这些括号表示真正的解引用,而在 leaq
中则不是,它们只是方便的语法。
[1] 我已经说过,当 array
是一个 long
数组时,表达式 array[i]
会加载地址 array + i * sizeof(long)
上的值。这是正确的,但应该注意一个细节。如果我写下 C 代码
long x = array[5];
这与打字不相同
long x = *(array + 5 * sizeof(long));
看起来应该基于我的先前陈述,但实际上并不是这样。
问题在于C指针加法有一个技巧。假设我有一个指向类型为T
的值的指针p
。表达式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
操作只是算术运算,它的参数实际上不必引用有效地址。因此,它经常用于对可能不打算被取消引用的值执行纯算术运算。例如,带有-O2
优化的cc
编译器。
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对地址计算的硬件支持来进行任意计算的。 - Peter Cordesarray + i
还是 array + i * sizeof(long)
。我决定采用后者,因为我们处于汇编的上下文中,并且我从未在 C 语句中使用过表达式 array + i
。但是,正如你所指出的那样,它仍然是有效的 C 语法。我会添加一个脚注。 - Quelklef如果只指定一个文字,那么没有区别。不过,LEA 有更多的功能,您可以在此处阅读有关它们的信息:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
.bss .lcomm LabelFromBssSegment, 4
这样的东西时,你就不能真正地使用leal TextLabel, LabelFromBssSegment
,是吗?你必须要使用movl $TextLabel, LabelFromBssSegment
,对吧? - JSmythlea
需要一个寄存器目标,但mov
可以有一个imm32
源和一个内存目标。这个限制当然不是特定于GNU汇编器的。 - Peter CordesMOV AX, [TABLE-ADDR]
的,它是一种加载操作。因此有一个重大的区别。相应的指令是 mov ax, OFFSET table_addr
。 - Peter Cordes如其他答案所述:
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示例。 - Peter Cordesleaq (%rax,%rax), %rdx
的意思是 rdx = rax + 1 * rax
?我的clang反汇编器应该这样写,而不是这样写:leaq (%rax,%rax,), %rdx
。请注意多余的逗号。因为这个括号部分是一个三元表达式,而不是二元表达式。 - BitTickler这取决于所使用的汇编器,因为
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指令需要其源操作数为内存操作数,但需要取地址。这只是ISA的一部分,除非您想发明全新的LEA语法,尽管它使用正常的寻址模式编码。像lea rax,[rdi + rsi * 4]
这样的东西很有用,或者在64位模式下进行RIP相对LEA。) - Peter Cordes基本上...在计算之后“移动到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。 - Peter CordesMOV指令可以像LEA [label]一样完成相同的操作,但MOV指令在指令本身中包含有效地址作为立即数常量(由汇编程序提前计算)。LEA使用PC相对地址在执行指令期间计算有效地址。
[label]
不是RIP相对寻址模式的正确语法。但是,是的,这是准确的。如何在GNU Assembler中将函数或标签的地址加载到寄存器中更详细地解释了这一点。 - Peter Cordes