我今天做了一个测试,唯一不明白的问题是将双字转换为四字。
这让我想到,为什么/什么时候我们为乘法或除法签署扩展?另外,我们什么时候使用cdq这样的指令?
最佳答案
使用 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
如果在
idiv
、 you can get a large positive result for -5 / 2, for example 之前将 EDX/RDX 归零而不是符号扩展到 EDX:EAX 。使用 64/32 位 => 32 位除法的“全功率”是可能的,但除非您知道除数足够大以便商不会溢出,否则不安全。 (即,您通常无法仅使用
(a*b) / c
/mul
和 EDX:EAX 中的 64 位临时文件来实现 div
。)除法在商溢出时引发异常 (#DE)。在 Unix/Linux 上,the kernel delivers SIGFPE 用于算术异常,包括除法错误。使用正常符号或零扩展除法,溢出仅可能为 with
idiv
of INT_MIN / -1
(即最负数的 2 的补码特例。)正如您从 insn ref 手册中看到的(链接在 x86 标签维基中):
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 位指令是 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
执行任何操作。如果您的输入已签名,则对其进行签名扩展以填充您用作乘法输入的寄存器,例如使用
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 的移位直到 186 才存在,并且每个计数仍然是 1 个周期,因此在 8086 上,您必须做更糟糕的事情(例如分支,或将符号位旋转到底部并隔离 + neg
它)。所以8086中的cwd
在需要的时候节省了大量的时间/空间。在 64 位模式下,符号和零将 32 位值扩展到 64 位是常见的。 ABI 允许在保存 32 位值的 64 位寄存器的高 32 位中存在垃圾,因此如果您的函数只应该查看
edi
的低 32 位,则不能仅使用 [array + rdi]
来索引数组。所以你会看到很多
movsx rdi, edi
(符号扩展)或 mov eax, edi
(零扩展,是的,使用不同的目标寄存器更有效,因为英特尔 mov-elimination 不适用于 mov same,same
)
关于assembly - 我们何时以及为何签署扩展并使用带有 mul/div 的 cdq?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36464879/