ChatGPT解决这个技术问题 Extra ChatGPT

MOV和LEA有什么区别?

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

MOV AX, [TABLE-ADDR]

LEA AX, [TABLE-ADDR]
谢谢尼克。首先,通过查看该链接,我不会找到这个问题的答案。在这里,我正在寻找特定信息,您提供的链接中的讨论本质上更一般。
我很久以前就支持@Nick 的 dup 了,但现在才 vtc。回想起来,我太仓促了,现在天真地认为a)另一个问题没有回答“有什么区别”,b)这是一个有用的问题。为我的错误向 naveen 道歉——如果我能撤消 vtc 就好了……
相关:Using LEA on values that aren't addresses / pointers? 讨论 LEA 的其他用途,用于任意数学。

E
Evan Carroll

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 的最简单用法重叠。如果您有多个基地址等的多部分计算,它很有用。


让我感到困惑的是 lea 名称中有“加载”,人们说它将计算的地址“加载”到寄存器中,因为计算内存位置的所有输入都是立即值或寄存器。 AFAICT lea 只执行计算,它不加载任何东西,加载意味着接触内存?
@josephGarvin IIRC 术语 fetch 将应用于该方面;加载就是你如何用从头开始的东西替换寄存器中的值。例如 LAHF 是:将 FLAGS 加载到 AH 寄存器中。在 CLR 的 CIL(它是一个更高级别的基于堆栈的抽象机器中,术语 load 是指将一个值放到概念堆栈上,通常是 l... 和 s。 ..等价的反之亦然)。这些注释:cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html) 表明确实存在适用于您的区别的架构。
这一切都让我想起了slideshare.net/pirhilton/…;)
@JosephGarvin:在计算机体系结构术语中,“加载”一词可用于任何写入寄存器的内容,如果您在谈论它在内部是如何工作的。值得注意的是,在 RISC ISA 中,load-immediate(只是将机器代码中的一个常量放入寄存器),如 MIPS lui $t0, 1(Load Upper Immediate),它将 $a0 设置为 1<<16,尽管在这种情况下该值是作为机器代码的一部分在内存中。 (或者在像 AArch64 这样的现代 ISA 中,以某种方式编码,而不是从字面上看。)是的,当代码中有“在 EDI 中存储 1”之类的注释时,它让我发疯。
正如 Ruben 所指出的,x86 也使用 LAHF 助记符,它写入一个 GP 寄存器。 (但我总是要查看它是加载 AH into flags 还是 from FLAGS,因为 FLAGS 也是一个寄存器。还有一个 SAHF 是另一个方向,阅读通用寄存器 AH。)
M
Michael Petch

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


仅在 NASM 语法中。在 MASM 语法中,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/optimizestackoverflow.com/tags/x86/info
B
Bill Forster

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

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


Q
Quelklef

以前的答案都没有完全解决我自己的困惑,所以我想添加我自己的。

我缺少的是 lea 操作处理括号的使用与 mov 不同。

想想 C。假设我有一个 long 数组,我称之为 array。现在表达式 array[i] 执行取消引用,从地址 array + i * sizeof(long) [1] 的内存中加载值。

另一方面,考虑表达式 &array[i]。这仍然包含子表达式 array[i],但不执行取消引用! array[i] 的含义发生了变化。它不再意味着执行遵从,而是充当一种规范,告诉&我们要查找的内存地址。如果您愿意,您也可以将 & 视为“取消”取消引用。

因为这两个用例在很多方面都相似,所以它们共享语法 array[i],但是 & 的存在与否会改变该语法的解释方式。如果没有 &,它是一个取消引用,实际上是从数组中读取的。对于 &,它不是。仍会计算值 array + i * sizeof(long),但不会取消引用。

movlea 的情况非常相似。对于 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 变为 %rbp8 是数组中包含的数据类型的长度。

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

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

正如使用 movq 对应于解引用一样,这里使用 leaq 对应于 not 解引用。该装配线对应于 C 线 x = &array[i];。回想一下 &array[i] 的含义从取消引用更改为简单地指定位置。同样,使用 leaq(%rdi, %rsi, 8) 的含义从取消引用更改为指定位置。

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

请注意,我对 leaqmovq 的描述之间的唯一区别是 movq 进行了取消引用,而 leaq 没有。其实写leaq的描述,我基本上是复制+粘贴了movq的描述,然后去掉“在这个位置找值”。

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

[1] 我说过,当 arraylong 的数组时,表达式 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

是的,很好的解释,比其他答案更详细,是的,C 的 & 运算符是一个很好的类比。或许值得指出的是,LEA 是一种特殊情况,而 MOV 就像其他可以获取内存或寄存器操作数的指令一样。例如 add (%rdi), %eax 只是使用寻址方式来寻址内存,与 MOV 相同。也相关:Using LEA on values that aren't addresses / pointers? 进一步解释:LEA 是您如何使用 CPU 的硬件对地址数学的支持来进行任意计算。
“在 %rdi 处获取值”——这是一个奇怪的措辞。您的意思是应该使用寄存器中的值 rdi。您对“at”的使用似乎意味着没有的内存取消引用。
@PeterCordes 谢谢!我已经在答案中添加了关于它是特例的观点。
@ecm 好点;我没有注意到这一点。我现在改了,谢谢! :)
最后一个技巧真是太棒了。编译器在提高 exe 效率方面确实做得很好。
C
Community

如果只指定文字,则没有区别。不过,LEA 有更多的能力,你可以在这里阅读它们:

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


我想,除了在 GNU 汇编器中,.bss 段中的标签不是真的吗? AFAIR 你真的不能leal TextLabel, LabelFromBssSegment当你得到smth。像 .bss .lcomm LabelFromBssSegment, 4,你必须要 movl $TextLabel, LabelFromBssSegment,不是吗?
@JSmyth:那只是因为 lea 需要一个寄存器目标,但 mov 可以有一个 imm32 源和一个内存目标。这个限制当然不是特定于 GNU 汇编器的。
此外,这个答案基本上是错误的,因为问题是在询问 MOV AX, [TABLE-ADDR],这是一个负担。所以有很大的不同。等效指令是 mov ax, OFFSET table_addr
链接已失效。
c
cokeman19

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

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

非常感谢,只是我不能将多个标记为答案:(
x86 指令 MOV 和 LEA 之间的区别绝对不取决于汇编程序。
我在汇编编程方面的鼎盛时期大约是 1984 年的 6502 :) 有人会认为,对于更复杂的体系结构(如 x86_64),汇编语法在这 40 年中会有所改进,并且不那么神秘和容易出错......
S
Sir Random

如其他答案所述:

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,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 注意多余的逗号。由于这个括号部分是三元表达式,而不是二元表达式。
O
Ostap

基本上......“移动到 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。
L
Luftatako

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

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


M
Michel Sayde

MOV 可以做与 LEA [label] 相同的事情,但 MOV 指令包含指令本身内部的有效地址作为立即常数(由汇编程序预先计算)。 LEA 在指令执行期间使用 PC-relative 计算有效地址。


这仅适用于 64 位模式(PC 相对寻址是新的);在其他模式下,lea [label 与更紧凑的 mov 相比浪费了毫无意义的字节,因此您应该指定您正在谈论的条件。此外,对于某些汇编程序,[label] 不是 RIP 相对寻址模式的正确语法。但是,是的,这是准确的。 How to load address of function or label into register in GNU Assembler 更详细地解释。
j
jojasicek

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


G
Guillermo Phillips

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

有效地使用 LEA 相当于在 C 等语言中使用指针,因为它是一个强大的指令。


我认为这个答案充其量是令人困惑的。 “LEA 指令是一条‘加载有效地址’,它是一条间接指令,这意味着 TABLE-ADDR 指向一个内存位置,在该内存位置找到要加载的地址。”实际上 LEA 将加载地址,而不是地址的内容。我认为实际上需要向提问者保证 MOV 和 LEA 可以重叠,并且在某些情况下做同样的事情