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

时间:2016-04-07 00:57:19

标签: assembly x86 sign-extension

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

这让我思考,为什么/我们何时签署扩展以进行乘法或除法?另外,我们什么时候使用像cdq这样的指令?

1 个答案:

答案 0 :(得分:10)

cdq / idiv用于签名的32位/ 32位=> 32位分区,
xor edx,edx / div表示未签名。

如果您在idiv you can get a large positive result for -5 / 2, for example之前将EDX / RDX归零而不是符号扩展到EDX:EAX。

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

除了商的溢出时,分区引发异常(#DE)。在Unix / Linux上,the kernel delivers SIGFPE用于算术异常,包括除法错误。使用正常符号或零扩展除法时,溢出只能with idiv of INT_MIN / -1(即最负数的2&#39补码特殊情况。)

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

  • 一个操作数mul / imuledx:eax = eax * src
  • 双操作数imuldst *= src。例如 imul ecx, esi 无法读取或写入eax或edx。
  • div / idiv:将edx:eax除以src。 eax中的商,edx中的余数。没有div / idiv形式忽略输入中的edx
  • cdqeax符号扩展为edx:eax,即将eax的符号位广播到edx的每一位。不要与cdqe混淆,这是一个64位指令,movsx rax, eax具有较少的insn字节。

    最初(8086),只有cbwax = sign_extend(al))和cwddx:ax = sign_extend(ax))。 x86到32bit和64bit的扩展使得助记符略微模糊(但请记住,除了cbw之外,ex内部版本总是以e为延伸结束。没有dl = sign_bit(al)指令,因为8位mul和div是特殊的,并且使用ax而不是dl:al

由于[i]mul的输入是单个寄存器,因此在乘法之前,您永远不需要对edx执行任何操作。

如果您的输入已签名,则对其进行签名扩展以填充您用作乘法输入的寄存器,例如使用movsxcwdeeax = sign_extend(ax))。如果您的输入是无符号的,则零延伸。 (除非您只需要乘法结果的低16位,例如it doesn't matter if the upper 16 bits of either or both inputs contain garbage。)

对于除法,您总是需要将扩展​​eax归零或签名到edx。零扩展与无条件归零edx相同,因此没有特殊的指令。只需xor edx,edx

cdq存在是因为它比mov edx, eax / sar edx, 31要短得多,以便将eax的符号位广播到edx中的每个位。此外,计数转移> 1直到286才存在,所以在8086,如果cwd不存在(cdq的16位版本),你需要一个循环。

在64位模式下,将32位值的符号和零扩展到64位是常见的。 ABI允许64位寄存器的高32位中的垃圾保持32位值,所以如果你的函数只能看edi的低32位,你就不能只使用[array + rdi]索引数组。

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