我想知道这些指令之间的区别是什么:
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
的最简单用法重叠。如果您有多个基地址等的多部分计算,它很有用。
在 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
是更糟糕的选择,除了 RIP 相对寻址的 64 位模式。 mov r32, imm32
在更多端口上运行。 lea eax, [edx*4]
是一种复制和移位,否则无法在一条指令中完成,但在同一个寄存器中 LEA 只需要更多字节来编码,因为 [eax*4]
需要 disp32=0
。 (不过,它运行在与班次不同的端口上。)请参阅 agner.org/optimize 和 stackoverflow.com/tags/x86/info。
指令 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。假设我有一个 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
对应于 not 解引用。该装配线对应于 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
not 表示“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 相同。也相关:Using LEA on values that aren't addresses / pointers? 进一步解释:LEA 是您如何使用 CPU 的硬件对地址数学的支持来进行任意计算。
%rdi
处获取值”——这是一个奇怪的措辞。您的意思是应该使用寄存器中的值 rdi
。您对“at”的使用似乎意味着没有的内存取消引用。
如果只指定文字,则没有区别。不过,LEA 有更多的能力,你可以在这里阅读它们:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
leal TextLabel, LabelFromBssSegment
当你得到smth。像 .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
它的工作原理相同。
如果 table_addr
是局部变量,lea
版本也可以正常工作,例如
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
如其他答案所述:
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,Using LEA on values that aren't addresses / pointers? 使用 AT&T,而我的回答还有其他一些 AT&T 示例。
leaq (%rax,%rax), %rdx
表示 rdx = rax + 1 * rax
?我的clang反汇编程序应该这样写,对吧? leaq (%rax,%rax,), %rdx
注意多余的逗号。由于这个括号部分是三元表达式,而不是二元表达式。
基本上......“移动到 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
is a shift-and-add instruction 使用内存操作数机器编码和语法,因为硬件已经知道如何解码 ModR/M + SIB + disp0/8/32。
让我们通过一个例子来理解这一点。
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 )。
MOV 可以做与 LEA [label] 相同的事情,但 MOV 指令包含指令本身内部的有效地址作为立即常数(由汇编程序预先计算)。 LEA 在指令执行期间使用 PC-relative 计算有效地址。
lea [label
与更紧凑的 mov
相比浪费了毫无意义的字节,因此您应该指定您正在谈论的条件。此外,对于某些汇编程序,[label]
不是 RIP 相对寻址模式的正确语法。但是,是的,这是准确的。 How to load address of function or label into register in GNU Assembler 更详细地解释。
LEA(加载有效地址)是移位加指令。它被添加到 8086 是因为硬件可以解码和计算寻址模式。
差异是微妙但重要的。 MOV 指令是一个“MOVe”,实际上是 TABLE-ADDR 标签所代表的地址的副本。 LEA 指令是“加载有效地址”,它是一条间接指令,这意味着 TABLE-ADDR 指向要加载的地址所在的内存位置。
有效地使用 LEA 相当于在 C 等语言中使用指针,因为它是一个强大的指令。
LAHF
是:将 FLAGS 加载到 AH 寄存器中。在 CLR 的 CIL(它是一个更高级别的基于堆栈的抽象机器中,术语 load 是指将一个值放到概念堆栈上,通常是l
... 和s
。 ..等价的反之亦然)。这些注释:cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html) 表明确实存在适用于您的区别的架构。lui $t0, 1
(Load Upper Immediate),它将$a0
设置为1<<16
,尽管在这种情况下该值是作为机器代码的一部分在内存中。 (或者在像 AArch64 这样的现代 ISA 中,以某种方式编码,而不是从字面上看。)是的,当代码中有“在 EDI 中存储 1”之类的注释时,它让我发疯。LAHF
助记符,它写入一个 GP 寄存器。 (但我总是要查看它是加载 AH into flags 还是 from FLAGS,因为 FLAGS 也是一个寄存器。还有一个 SAHF 是另一个方向,阅读通用寄存器 AH。)