我们何时以及为何签署扩展并使用带有 mul/div 的 cdq? [英] When and why do we sign extend and use cdq with mul/div?
问题描述
我今天做了一个测试,唯一不明白的问题是将双字转换为四字.
I had a test todayand the only question I didn't understand involved converting a doubleword to a quad word.
这让我想到,为什么/什么时候我们为乘法或除法签署扩展?另外,我们什么时候使用cdq这样的指令?
That got me thinking, why/when do we sign extend for multiplication or division? In addition, when do we use instructions like cdq?
推荐答案
使用 cdq
/idiv
对于有符号 32 位/32 位 => 32 位除法,xor edx,edx
/div
用于无符号.
Use cdq
/ idiv
for signed 32-bit / 32-bit => 32 bit division,
xor edx,edx
/ div
for unsigned.
以 EAX 中的被除数开始,并将除数指定为 DIV 或 IDIV 的操作数.
With the dividend in EAX to start with, and the divisor specified as an operand to DIV or IDIV.
mov eax, 1234
mov ecx, 17
cdq ; EDX = signbit(EAX)
idiv ecx ; EAX = 1234/17 EDX = 1234%17
如果在 idiv
之前将 EDX/RDX 归零而不是符号扩展到 EDX:EAX,例如,您可以获得 -5/2 的大正结果.
If you zero EDX/RDX instead of sign-extending into EDX:EAX before idiv
, you can get a large positive result for -5 / 2, for example.
使用 64/32 位的全功率"=> 32 位除法是可能的,但不安全,除非您知道除数足够大以便商不会溢出.(即您通常不能仅使用 mul
/div
和 EDX 中的 64 位临时文件来实现 (a*b)/c
:EAX.)
Using the "full power" of 64 / 32-bit => 32-bit division is possible, but not safe unless you know the divisor is large enough so the quotient doesn't overflow. (i.e. you can't in general implement (a*b) / c
with just mul
/ div
and a 64-bit temporary in EDX:EAX.)
除法在商溢出时引发异常 (#DE).在 Unix/Linux 上,内核为算术异常(包括除法错误)提供 SIGFPE.使用正常符号或零扩展除法,溢出仅可能 与 INT_MIN/-1
的 idiv
(即最负数的 2 的补码特例.)
Division raises an exception (#DE) on overflow of the quotient. On Unix/Linux, the kernel delivers SIGFPE for arithmetic exceptions including divide errors. With normal sign or zero-extended divide, overflow is only possible with idiv
of INT_MIN / -1
(i.e. the 2's complement special case of the most negative number.)
正如您从 insn ref 手册中看到的(x86 标签维基):
As you can see from the insn ref manual (link in the x86 tag 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
intoedx:eax
,即将eax
的符号位广播到edx
的每一位.不要与cdqe
混淆,64 位指令是movsxd rax, eax
的更紧凑形式.
div
/idiv
: dividesedx:eax
by the src. quotient ineax
, remainder inedx
. There's no form ofdiv
/idiv
that ignoresedx
in the input.cdq
sign-extendseax
intoedx:eax
, i.e. broadcasts the sign bit ofeax
into every bit ofedx
. Not to be confused withcdqe
, the 64-bit instruction that is a more compact form ofmovsxd 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
.
Originally (8086), there was just cbw
(ax = sign_extend(al)
) and cwd
(dx:ax = sign_extend(ax)
). The extensions of x86 to 32bit and 64bit have made the mnemonics slightly ambiguous (but remember, other than cbw
, the within-eax versions always end with an e
for Extend). There is no dl=sign_bit(al) instruction because 8bit mul and div are special, and use ax
instead of dl:al
.
因为 [i]mul
的输入是单个寄存器,所以在乘法之前你永远不需要对 edx
做任何事情.
Since the inputs to [i]mul
are single registers, you never need to do anything with edx
before a multiply.
如果您的输入已签名,则您可以对其进行签名扩展以填充您用作乘法输入的寄存器,例如使用 movsx
或 cwde
(eax = sign_extend(ax)
).如果您的输入是无符号的,则扩展为零.(除了如果您只需要乘法结果的低 16 位,例如 其中一个或两个输入的高 16 位是否包含垃圾并不重要.)
If your input is signed, you sign-extend it to fill the register you're using as an input to the multiply e.g. with movsx
or cwde
(eax = sign_extend(ax)
). If your input is unsigned, you zero extend. (With the exception that if you only need the low 16 bits of the multiply result, for example, it doesn't matter if the upper 16 bits of either or both inputs contain garbage.)
对于除法,您总是需要将 eax 扩展到 edx 或将其归零或符号化.零扩展与无条件将 edx 归零相同,因此没有特殊说明.只是xor edx,edx
.
For a divide, you always need to zero or sign extend eax into edx. Zero-extending is the same as just unconditionally zeroing edx, so there's no special instruction for it. Just xor edx,edx
.
cdq
存在是因为它比 mov edx, eax
/sar edx, 31
短很多,将 eax 的符号位广播到每个edx 中的位.此外,立即计数 > 1 的移位直到 186 才存在,并且每个计数仍然是 1 个周期,因此在 8086 上,您必须做更糟糕的事情(例如分支,或将符号位旋转到底部并隔离 + <代码>否定代码>它).所以 8086 中的 cwd
在需要时节省了大量的时间/空间.
cdq
exists because it's a lot shorter than mov edx, eax
/ sar edx, 31
to broadcast the sign bit of eax to every bit in edx. Also, shifts with immediate count > 1 didn't exist until 186 and were still 1 cycle per count, so on 8086 you'd have to do something even worse (like branch, or rotate the sign bit to the bottom and isolate + neg
it). So cwd
in 8086 saved a lot of time/space when it was needed.
在 64 位模式下,符号和零将 32 位值扩展到 64 位是很常见的.ABI 允许持有 32 位值的 64 位寄存器的高 32 位存在垃圾,因此如果您的函数只应该查看 edi
的低 32 位,则不能只使用 [array + rdi]
对数组进行索引.
In 64bit mode, sign and zero extending 32bit values to 64bit is common. The ABI allows garbage in the high 32bits of a 64bit register holding a 32bit value, so if your function is only supposed to look at the low 32bits of edi
, you can't just use [array + rdi]
to index the array.
所以你会看到很多 movsx rdi, edi
(符号扩展)或 mov eax, edi
(零扩展,是的,使用不同的目标寄存器,因为英特尔 mov-elimination 不适用于 mov same,same
)
So you see a lot of movsx rdi, edi
(sign extend), or mov eax, edi
(zero-extend, and yes it's more efficient to use a different target register, because Intel mov-elimination doesn't work with mov same,same
)
这篇关于我们何时以及为何签署扩展并使用带有 mul/div 的 cdq?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!