当操作数大小相同时,为什么 MOVZX 不起作用?

Why doesn't MOVZX work when operands have the same size?

对于 Z2 dword ?mov eax, Z2 工作正常但 movzx eax, Z2 给出“无效指令操作数”错误。

我在这里有点困惑:即使 Z2eax 大小相同, 为什么程序集不能为此接受 movzx?似乎 movzx 特别希望操作数的大小不同。

设计这样一条指令的原因可能是什么?

如果将代码设计为仅允许相同大小的操作数,是否会更容易编写代码?

它确实有效(在机器代码中),但效率低下。
这就是为什么大多数 assemble 会阻止你搬起石头砸自己的脚。


What could be the reason for designing an instruction like this ?

从窄源数据执行零扩展。
助记词中的ZX就是这个意思

如果你有相同大小的操作数,你应该使用 mov,
不要尝试使用零扩展或符号扩展复制指令。


就像 MOVSXD 一样,即使可以使用 MOVZX 操作码来编码相当于 mov r, r/m16 的指令,出于效率原因也不推荐这样做。

喜欢Intel says for MOVSXD不鼓励使用没有REX.W(会编码movsxd r32, r/m32)的MOVSXD 。应该使用常规 MOV 而不是使用没有 REX.W. 的 MOVSXD(我从引述中删除了“在 64 位模式下”,因为那是多余的;movsxd 仅存在于 64-位模式;操作码在其他模式下有其他含义。)


无论如何,可以在 x86 机器代码中 movzx ax, bx,但是 assemble 可以让你远离自己并拒绝 assemble 那个低效的指令。 mov 的 2 字节操作码而不是 1;movzx 是 386 中的新操作码,所有 1 字节操作码在此之前已经用完。)

Copies the contents of the source operand (register or memory location) to the destination operand (register) and zero extends the value. The size of the converted value depends on the operand-size attribute.
https://www.felixcloutier.com/x86/movzx

我在我的 Skylake CPU 上用下面的 NASM 源测试了它,也可能用 MASM 写入了 assemble。 (例如 db 66h 而不是在 movzx 行上使用 o16 NASM 前缀。)

mov  edx, -1
xor  eax,eax
db   66h             ; operand-size prefix that we're not telling the assembler about
movzx  eax, dx

mov  ax, dx          ; for comparison

(超级小,利用工具链默认值来完成这个一次性程序,这从来没有打算成为一个合适的程序。)

$ nasm -felf64 movzx.asm && ld -o movzx  movzx.o 
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
$ objdump -drwC -Mintel  ./movzx
...
  401000:       ba ff ff ff ff          mov    edx,0xffffffff
  401005:       48 b8 cc cc cc cc 44 33 22 11   movabs rax,0x11223344cccccccc
  40100f:       66 0f b7 c2             movzx  ax,dx
  401013:       66 89 d0                mov    ax,dx       # note it's shorter.  
          # Fun fact: we can see NASM picked the mov r/m16, r form, since the ModRM byte is different.

有趣的是,GNU Binutils(objdump -d 和 GDB)中的 disassembler 将其解码为 movzx ax, dx,或 AT&T 语法中的 movzww %dx, %ax

在静态可执行文件上使用 gdb ./movzx,我使用 layout regstarti / stepi 单步执行并查看寄存器更改:

66 0f b7 c2 movzx ax,dx正常执行,
将 RAX 从 0x11223344cccccccc 更改为 0x11223344ccccffff,证明它的行为与 16 位 mov 完全一样,不触及 RAX 的任何高位字节。 (包括不隐式零扩展 RAX 的高 32 位,就像写入 EAX 一样。)

(然后退出GDB,因为我没有包含退出的代码,只有我真正想单步执行的代码。)


这对于 movzx al, dl 是不可能的 - 16 位与 32 位与 64 位操作数大小由 66 或 REX 前缀选择以覆盖模式的默认值,但 8 位操作数大小只能通过操作码设置。没有前缀可以覆盖 8 位操作数大小的指令。当然,没有带有 8 位目标操作数的 movzx 形式。 (如果你想将一个半字节零扩展为一个字节,复制并 and reg, 0x0f。)


允许它的汇编程序:只是 .intel_syntax 模式下的 GAS?

NASM 并且 YASM 拒绝 movzx ax, dx
clang 也是如此(.intel_syntax noprefix)。
但是 llvm-objdump -d 会发现 assemble 它与 GNU Binutils 相同。

但是 GNU Binutils 不仅发现assemble它(Intel movzx ax,dx、AT&T movzww %dx, %ax),它(GNU as) 接受 英特尔语法版本。气体:

.intel_syntax noprefix
    movzx  ax, dx             # works, producing the above machine code.

.att_syntax
    movzw   %dx, %ax         # Error: operand size mismatch for `movzw'
    movzww  %dx, %ax         # Error: invalid instruction suffix for `movzw'

相关:

  • MOVZX missing 32 bit register to 64 bit register

It seems that movzx specifically wants that the operands are not of same size

movzx 特别希望目标大于源。

why couldn't assembly just accept movzx for this

理论上; assembler 没有理由不接受“具有相同大小的操作数的 movzx(助记符)”并默默地生成 mov(操作码)。

What could be the reason for designing an instruction like this ?

人都会犯错。对于所有编程语言,最好尽快检测并报告错误(理想情况下,在 IDE 中,这样您甚至不必编译或 assemble 就可以找到错误;并且从未检测到由普通用户通过软件发布后的错误报告报告。

对于“movzx 具有相同大小的操作数”,它更可能是一个错误(例如,程序员希望将更小的东西用零扩展成更大的东西,但他们输入了错误的操作数)而不是是有意的(假设 mov 会更容易输入);所以 assembler 最好将其视为错误,这样(如果是错误)程序员可以更快地知道它。

请注意,在某些情况下,某些“由 assembler 完成的方便的静默替换”会有所帮助。一个例子是 movzx rax,eax,很明显程序员想要用零扩展一些更小的东西以变得更大;但是 assembler 最好生成一个 mov eax,eax,因为 CPU 默认情况下会零扩展。