我今天接受了测试,我唯一明白的问题是将双字转换为四字。
这让我思考,为什么/我们何时签署扩展以进行乘法或除法?另外,我们什么时候使用像cdq这样的指令?
答案 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参考手册(x86标签wiki中的链接)中看到的那样:
mul
/ imul
:edx:eax = eax * src
imul
:dst *= src
。例如 imul ecx, esi
无法读取或写入eax或edx。div
/ idiv
:将edx:eax
除以src。 eax
中的商,edx
中的余数。没有div
/ idiv
形式忽略输入中的edx
。 cdq
将eax
符号扩展为edx:eax
,即将eax
的符号位广播到edx
的每一位。不要与cdqe
混淆,这是一个64位指令,movsx rax, eax
具有较少的insn字节。
最初(8086),只有cbw
(ax = sign_extend(al)
)和cwd
(dx:ax = sign_extend(ax)
)。 x86到32bit和64bit的扩展使得助记符略微模糊(但请记住,除了cbw
之外,ex内部版本总是以e
为延伸结束。没有dl = sign_bit(al)指令,因为8位mul和div是特殊的,并且使用ax
而不是dl:al
。
由于[i]mul
的输入是单个寄存器,因此在乘法之前,您永远不需要对edx
执行任何操作。
如果您的输入已签名,则对其进行签名扩展以填充您用作乘法输入的寄存器,例如使用movsx
或cwde
(eax = 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
)