我想知道这些指令之间的区别是什么:
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 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]
,但是存在或不存在 a&
会改变该语法的解释方式。没有&
,它是一个取消引用,实际上是从数组中读取的。,&
它不是。该值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];
, where array
become%rdi
和i
become%rsi
和x
become %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
请注意,我对and的描述之间的唯一区别movq
是movq
取消引用,而leaq
没有。其实写leaq
描述,我基本上是复制+粘贴的描述movq
,然后去掉“在这个位置找值”。
总结一下:movq
vs.leaq
很棘手,因为它们对括号的使用,如(%rsi)
and (%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 指针添加有一个技巧。假设我有一个指向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 有更多的能力,你可以在这里阅读它们:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
这取决于使用的汇编程序,因为
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
如其他答案所述:
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 */
基本上......“移动到 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 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(加载有效地址)是移位加指令。它被添加到 8086 是因为硬件可以解码和计算寻址模式。
差异是微妙但重要的。MOV 指令是一个“MOVe”,实际上是 TABLE-ADDR 标签所代表的地址的副本。LEA 指令是“加载有效地址”,它是一条间接指令,这意味着 TABLE-ADDR 指向要加载的地址所在的内存位置。
有效地使用 LEA 相当于在 C 等语言中使用指针,因为它是一个强大的指令。