我们何时以及为何签署扩展并使用 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 在 x86 标签 wiki 中):
- 单操作数
mul
/ imul
: edx:eax = eax * src
- 双操作数
imul
:dst *= src
。例如imul ecx, esi
不读取或写入 eax 或 edx。
div
/ idiv
: divides edx:eax
来源。 eax
中的商,edx
中的余数。输入中没有忽略 edx
的 div
/ idiv
形式。
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 位,例如 。)
对于 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
)
一起工作
我今天考试了,我唯一不明白的问题是将双字转换为四字。
这让我开始思考,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
、
使用 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
正如你从 insn ref 手册中看到的那样(link 在 x86 标签 wiki 中):
- 单操作数
mul
/imul
:edx:eax = eax * src
- 双操作数
imul
:dst *= src
。例如imul ecx, esi
不读取或写入 eax 或 edx。
div
/idiv
: dividesedx:eax
来源。eax
中的商,edx
中的余数。输入中没有忽略edx
的div
/idiv
形式。
的更紧凑形式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 位,例如
对于 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
)