x86汇编指针

35

我试图理解汇编中的指针。

那么这两者到底有什么不同呢:

mov eax, ebx

mov [eax], ebx

何时应使用 dword ptr [eax]

另外,当我尝试执行 mov eax, [ebx] 时,我会收到编译错误,为什么会这样?


6
mov eax, ebx 将寄存器 EBX 中的值移动到 EAX 中。mov [eax], ebx 将寄存器 EBX 中的 32 位值移动到 EAX 所指向的内存位置。 - Michael Petch
1
好的,只是为了确保我理解正确。以下代码:`int a;_asm { mov [a], eax`将把存储在eax中的值复制到int a指向的内存位置?如果我继续打印a的值,我会看到存储在eax中的值吗?另外,如果我改成以下方式: `int a = 5;_asm{ mov [eax], a `那里发生了什么?eax是否存储a的内存位置?还是它存储了5? - Duxa
1
@Duxa:这并不简单。如果我们假设a是一个变量,那么mov [a],ebx将把ebx中的值写入该变量(a也被视为地址)。一些编译器也允许mov a,ebx执行相同的操作,而另一些则不行。但是eax是一个寄存器,因此它不是一个变量。mov [eax],ebxmov eax,ebx不同。前者将ebx存储在eax指向的地址中,而后者将ebx存储在eax中。 - Rudy Velthuis
Rudy,我想你可能刚刚为我解决了一个巨大的困惑点。我正在使用Visual Studio(内联汇编),并试图弄清楚mov a,eaxmov [a],eax之间的区别。当查看寄存器和本地值时,它们是相同的...所以你是说它们是相同的吗?太好了,我知道了! - Duxa
1
在MASM语法中,方括号[]会被忽略,除非它们包含寄存器的名称。 - Ross Ridge
谢谢!这为我澄清了很多问题。现在最后一件事,我不明白为什么我不能执行 mov a, [eax]。难道这不应该使“a”成为指向eax所指的任何地方的指针吗? - Duxa
1个回答

61

正如已经提到的那样,将括号包围在操作数周围意味着该操作数被解引用,就像在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大小的内存位置。

当对寄存器操作数进行取消引用时,您根本不需要这样做,因为寄存器的名称指示其大小。 alah始终是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

现在,aeax 的值相同。如果 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指令中。现在回去改正它已经太晚了;你只需要熟悉语法,有时需要再看第二眼。



8
很难想象你要写这么多段来解释解引用操作符,令人难以置信。 - Cauterite
1
这种表示法在其他非强类型汇编器(如NASM)中是必需的/当解引用寄存器操作数时根本不需要这个。那段话有些混乱,需要重新写一下。在NASM中,非内存操作数是一个寄存器意味着操作数大小,因此无论[mem][edx]还是[var],你只需要使用op [mem], imm(或者inc [mem]movzxshl [mem], cl)来覆盖大小。而寻址模式中的寄存器决定地址大小而不是操作数大小。 - Peter Cordes
在MASM中,您可能需要使用大小覆盖来加载/存储与声明不匹配的大小,因此,在MASM中使用不涉及“变量”的寻址模式意味着如果另一个操作数是寄存器,则不需要大小覆盖。 我很确定你知道这一点(至少现在知道了),但这不是你写的内容。 : /您似乎对始终包括冗长的操作数大小说明符有不同的看法,而不仅在需要时才包括,因此我不愿意重新编写整个段落。 - Peter Cordes
关于不允许使用 mov mem,mem:我们有一些相关的问答,我认为最好的参考是 为什么不允许从内存到内存进行 movl 操作? / 哪些 x86 指令需要两个(或更多)内存操作数? - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接