12

我今天有一个测试,我唯一不明白的问题涉及将双字转换为四字。

这让我想到,为什么/什么时候我们为乘法或除法签名扩展?另外,我们什么时候使用cdq之类的指令呢?

4

1 回答 1

21

使用cdq/idiv表示有符号的 32 位 / 32 位 => 32 位除法,
xor edx,edx/div表示无符号。

以 EAX 中的除数开头,除数指定为 DIV 或 IDIV 的操作数。

   mov  eax, 1234
   mov  ecx, 17
   cdq                   ; EDX = signbit(EAX)
   idiv  ecx             ; EAX = 1234/17     EDX = 1234%17

如果您将 EDX/RDX 归零而不是在 之前将符号扩展到 EDX:EAX idiv您可以获得 -5 / 2 的大正结果,例如

使用 64 / 32 位 => 32 位除法的“全功率”是可能的,但不安全,除非您知道除数足够大以使商不会溢出。(即,您通常不能在 EDX:EAX 中(a*b) / c仅使用mul/div和 64 位临时实现。)

除法会在商溢出时引发异常 (#DE)。在 Unix/Linux 上,内核为包括除法错误在内的算术异常提供 SIGFPE。对于普通符号或零扩展除法,溢出仅可能idivofINT_MIN / -1(即最负数的 2 的补码特殊情况。)


正如您从 insn 参考手册(标签 wiki 中的链接)中看到的:

  • 单操作数mul/ imuledx:eax = eax * src
  • 二操作数imuldst *= src。例如imul ecx, esi,不读取或写入 eax 或 edx。

  • div/ idiv:除以edx:eaxsrc。商eax,余数edx。输入中没有忽略div/的形式。idivedx
  • cdq符号扩展eaxedx:eax,即将 的符号位广播eax到 的每一位中edxcdqe不要与64 位指令混淆,它是movsxd rax, eax.

    最初 (8086) 只有cbw( ax = sign_extend(al)) 和cwd( dx:ax = sign_extend(ax))。x86 到 32 位和 64 位的扩展使助记符有点模棱两可(但请记住,除了cbw,内 eax 版本总是以efor Extend 结尾)。没有 dl=sign_bit(al) 指令,因为 8bit mul 和 div 是特殊的,并且使用ax而不是dl:al.


由于输入是单个寄存器,因此在乘法之前[i]mul您不需要做任何事情。edx

如果您的输入是有符号的,您可以对其进行符号扩展以填充您用作乘法输入的寄存器,例如使用movsxcwde( eax = sign_extend(ax))。如果您的输入是无符号的,则您将零扩展。(例如,如果您只需要乘法结果的低 16 位,则其中一个或两个输入的高 16 位是否包含垃圾都无关紧要。)


对于除法,您总是需要将 eax 归零或符号扩展为 edx。零扩展与只是无条件地将 edx 归零相同,因此没有特殊说明。只是xor edx,edx

cdq之所以存在,是因为它比mov edx, eax/sar edx, 31将 eax 的符号位广播到 edx 中的每一位要短得多。此外,立即计数> 1 的移位直到 186 才存在,并且仍然是每个计数 1 个周期,所以在 8086 上你必须做一些更糟糕的事情(比如分支,或者将符号位旋转到底部并隔离 +neg它)。因此cwd,8086 在需要时节省了大量时间/空间。


在 64 位模式下,符号和零将 32 位值扩展到 64 位是常见的。ABI 允许在 64 位寄存器的高 32 位中包含 32 位值的垃圾,因此,如果您的函数只应该查看 的低 32 位edi,则不能仅用于[array + rdi]索引数组。

所以你会看到很多movsx rdi, edi(符号扩展)或mov eax, edi(零扩展,是的,使用不同的目标寄存器更有效,因为英特尔 mov-elimination 不适用于mov same,same

于 2016-04-07T01:18:26.217 回答