我们何时以及为何签署扩展并使用 mul/div 的 cdq?

When and why do we sign extend and use cdq with mul/div?

我今天考试了,我唯一不明白的问题是将双字转换为四字。

这让我开始思考,why/when 我们对乘法或除法进行扩展签名吗?另外,我们什么时候用cdq这样的指令?

对带符号的 32 位/32 位使用 cdq / idiv=> 32 位 division,
xor edx,edx / div 表示无符号。

以 EAX 中的 dividend 开始,并且 divisor 指定为 DIV 或 IDIV 的操作数。

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

如果在 idiv.

之前将 EDX/RDX 置零而不是符号扩展到 EDX:EAX

使用 64 / 32 位的 "full power" => 32 位 division 是可能的,但不安全,除非你知道 divisor 足够大,所以商不会溢出。 (即,您通常不能仅使用 mul / div 和 EDX:EAX 中的 64 位临时文件来实现 (a*b) / c。)

除法在商溢出时引发异常 (#DE)。在 Unix/Linux、the kernel delivers SIGFPE for arithmetic exceptions including divide errors. With normal sign or zero-extended divide, overflow is only possible 上(即最负数的 2 的补码特例。)


正如你从 insn ref 手册中看到的那样(link 在 标签 wiki 中):

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

  • div / idiv: divides edx:eax 来源。 eax 中的商,edx 中的余数。输入中没有忽略 edxdiv / idiv 形式。
  • cdqeax 符号扩展为 edx:eax,即将 eax 的符号位广播到 edx 的每一位.不要与 cdqe 混淆,64 位指令是 movsxd rax, eax.

    的更紧凑形式

    最初 (8086) 只有 cbw (ax = sign_extend(al)) 和 cwd (dx:ax = sign_extend(ax))。 x86 对 32 位和 64 位的扩展使助记符有些模糊(但请记住,除了 cbw 之外,eax 内的版本总是以 e 结尾,表示扩展)。没有dl=sign_bit(al)指令因为8bit mul和div比较特殊,用ax代替dl:al.


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

如果您的输入是有符号的,您可以对其进行符号扩展以填充您用作乘法输入的寄存器,例如使用 movsxcwde (eax = sign_extend(ax))。如果您的输入是无符号的,则您将扩展为零。 (除非您只需要乘法结果的低 16 位,例如 。)


对于 divide,您始终需要将 eax 归零或符号扩展到 edx。零扩展与无条件地将 edx 置零相同,因此没有特殊说明。就 xor edx,edx.

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


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

所以你看到很多movsx rdi, edi(符号扩展),或mov eax, edi(零扩展,是的,使用不同的目标寄存器更有效,因为英特尔移动消除不'与 mov same,same)

一起工作