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

5 x86 assembly sign-extension

我今天进行了测试,我唯一不理解的问题是将双字转换为四字.

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

Pet*_*des 9

使用cdq / idiv对于符号的32位/ 32位=> 32位划分,
xor edx,edx / div为无符号的.

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

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

在商的溢出时,除法引发异常(#DE).在Unix/Linux上,内核为算术异常(包括除法错误)提供SIGFPE.随着正常的标志或零扩展鸿沟,溢出只能idivINT_MIN / -1(最负数的也就是2的补特殊情况.)


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

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

  • div/ idiv:除以edx:eaxsrc.商eax,余数in edx.在输入中没有div/ idiv忽略的形式edx.
  • cdqsign-extends eaxinto edx:eax,即将符号位广播eax到每一位edx.不要混淆cdqe,movsx rax, eax使用较少insn字节的64位指令.

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


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

如果您的输入已签名,则对其进行签名扩展以填充您用作乘法的输入的寄存器,例如with movsxcwde(eax = sign_extend(ax)).如果您的输入是无符号的,则零延伸.(例外情况是,如果您只需要乘法结果的低16位,那么任何一个或两个输入的高16位是否包含垃圾都无关紧要.)


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

cdq之所以存在是因为它比短了很多mov edx, eax/ sar edx, 31到EAX的符号位广播到EDX每一位.此外,计数> 1的班次直到286才存在,所以在8086,你需要一个循环,如果cwd不存在(16位版本cdq).


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

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