我们何时以及为何签署扩展并使用带有 mul/div 的 cdq? [英] When and why do we sign extend and use cdq with mul/div?

查看:19
本文介绍了我们何时以及为何签署扩展并使用带有 mul/div 的 cdq?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我今天做了一个测试,唯一不明白的问题是将双字转换为四字.

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/-1idiv(即最负数的 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 手册中看到的( 标签维基):

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 into edx:eax,即将eax的符号位广播到edx的每一位.不要与 cdqe 混淆,64 位指令是 movsxd rax, eax 的更紧凑形式.

  • div / idiv: divides edx:eax by the src. quotient in eax, remainder in edx. There's no form of div / idiv that ignores edx in the input.
  • cdq sign-extends eax into edx:eax, i.e. broadcasts the sign bit of eax into every bit of edx. Not to be confused with cdqe, the 64-bit instruction that is a more compact form of 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.

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.

如果您的输入已签名,则您可以对其进行签名扩展以填充您用作乘法输入的寄存器,例如使用 movsxcwde (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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆