我试图理解汇编中的指针。
那么这两者到底有什么不同呢:
mov eax, ebx
和
mov [eax], ebx
何时应使用 dword ptr [eax]
?
另外,当我尝试执行 mov eax, [ebx]
时,我会收到编译错误,为什么会这样?
正如已经提到的那样,将括号包围在操作数周围意味着该操作数被解引用,就像在C中是指针一样。换句话说,括号意味着你正在从那个内存位置读取一个值(或将一个值存储到内存位置中),而不是直接读取那个值。
因此,这个:
mov eax, ebx
将ebx
中的值简单地复制到eax
中。用伪C符号表示,则为:eax = ebx
。
而这个代码片段:
mov eax, [ebx]
将 ebx
内容解引用并将指向的值存储在 eax
中。用伪C表示,则为:eax = *ebx
。
最后,这个:
mov [eax], ebx
将ebx
中的值存储到由eax
指向的内存位置中。同样,用伪C表示为:*eax = ebx
。
这里的寄存器也可以替换为内存操作数,例如符号变量名。因此,下面的内容:
mov eax, [myVar]
解引用变量 myVar
的地址,并将该变量的内容存储在 eax
中,就像 eax = myVar
。
相比之下,这个操作:
mov eax, myVar
将变量myVar
的地址存储到eax
中,就像eax = &myVar
。
至少,这是大多数汇编器的工作原理。Microsoft的汇编器(称为MASM)和Microsoft C / C ++编译器的内联汇编有点不同。它将上面两条指令视为等效,基本上忽略内存操作数周围的括号。
要在MASM中获取变量的地址,可以使用OFFSET
关键字:
mov eax, OFFSET myVar
不过,尽管MASM具有容错的语法并允许您懒散地编写代码,但您也不应该这样做。当您要取消引用变量并获取其实际值时,请始终包括括号。如果您明确使用适当的语法编写代码,则永远不会得到错误的结果,并且这将使其他人更容易理解。此外,它将强迫您养成编写代码的习惯,使其符合其他汇编器期望的编写方式,而不是依赖于MASM的“我所写的并非我所想”。
说到“我所写的并非我所想”,MASM通常也允许您省略操作数大小说明符,因为它知道变量的大小。但同样出于清晰和一致性的原因,我建议写出来。因此,如果myVar
是一个int
,您应该这样做:
mov eax, DWORD PTR [myVar] ; eax = myVar
或者mov DWORD PTR [myVar], eax ; myVar = eax
在其他没有强类型记忆的汇编程序(如NASM)中,这种表示法必要,以便记住myVar
是一个DWORD
大小的内存位置。
当对寄存器操作数进行取消引用时,您根本不需要这样做,因为寄存器的名称指示其大小。 al
和ah
始终是BYTE
大小,ax
始终是WORD
大小,eax
始终是DWORD
大小,rax
始终是QWORD
大小。但是,如果您愿意,包含它也无妨,以保持与表示内存操作数的方式一致。
另外,当我尝试执行
mov eax,[ebx]
时,我会收到编译错误,为什么?
嗯...你不应该。 在我的MSVC内联汇编中,它可以编译通过。正如我们已经看到的那样,它等效于:
mov eax, DWORD PTR [ebx]
并且意味着ebx
指向的内存位置将被解引用,并且DWORD
大小的值将被加载到eax
中。
为什么我不能执行
mov a, [eax]
?这不应该使得“a”成为指向eax所指向位置的指针吗?
不可以。这种操作数的组合是不允许的。正如您从MOV指令的文档中所看到的那样,基本上有五种可能性(忽略备用编码和段):
mov register, register ; copy one register to another
mov register, memory ; load value from memory into register
mov memory, register ; store value from register into memory
mov register, immediate ; move immediate value (constant) into register
mov memory, immediate ; store immediate value (constant) in memory
请注意,没有mov memory,memory
,这就是你所尝试的。
然而,你可以通过简单编码使a
指向eax
指向的内容:
mov DWORD PTR [a], eax
现在,a
和 eax
的值相同。如果 eax
是一个指针,那么 a
现在就是指向同一内存位置的指针。
如果你想将 a
设置为 eax
指向的值,则需要执行以下操作:
mov eax, DWORD PTR [eax] ; eax = *eax
mov DWORD PTR [a], eax ; a = eax
当然,这会覆盖指针并将其替换为解引用的值。 如果你不想失去指针,那么你将不得不使用第二个“备用”寄存器;类似于:
mov edx, DWORD PTR [eax] ; edx = *eax
mov DWORD PTR [a], edx ; a = edx
我知道这可能有些令人困惑。在x86 ISA中,mov
指令涵盖了许多潜在的意义,这是由于x86作为CISC架构的根源所致。相比之下,现代RISC架构更好地分离了寄存器-寄存器移动、内存加载和内存存储。x86将它们全部塞进一个单一的mov
指令中。现在回去改正它已经太晚了;你只需要熟悉语法,有时需要再看第二眼。
[mem]
是[edx]
还是[var]
,你只需要使用op [mem], imm
(或者inc [mem]
、movzx
或shl [mem], cl
)来覆盖大小。而寻址模式中的寄存器决定地址大小而不是操作数大小。 - Peter Cordesmov mem,mem
:我们有一些相关的问答,我认为最好的参考是 为什么不允许从内存到内存进行 movl 操作? / 哪些 x86 指令需要两个(或更多)内存操作数?。 - Peter Cordes
mov eax, ebx
将寄存器 EBX 中的值移动到 EAX 中。mov [eax], ebx
将寄存器 EBX 中的 32 位值移动到 EAX 所指向的内存位置。 - Michael Petcha
是一个变量,那么mov [a],ebx
将把ebx中的值写入该变量(a
也被视为地址)。一些编译器也允许mov a,ebx
执行相同的操作,而另一些则不行。但是eax
是一个寄存器,因此它不是一个变量。mov [eax],ebx
与mov eax,ebx
不同。前者将ebx存储在eax指向的地址中,而后者将ebx存储在eax中。 - Rudy Velthuismov a,eax
和mov [a],eax
之间的区别。当查看寄存器和本地值时,它们是相同的...所以你是说它们是相同的吗?太好了,我知道了! - Duxa[]
会被忽略,除非它们包含寄存器的名称。 - Ross Ridgemov a, [eax]
。难道这不应该使“a”成为指向eax所指的任何地方的指针吗? - Duxa