x86上的有符号和无符号算术实现
C语言有签名和无符号类型,如char和int。 我不确定,它是如何在汇编级别实现的,例如在我看来,有符号和无符号的乘法会带来不同的结果,所以汇编是做无符号和有符号算术还是只有一个,这在某种程度上是模拟的不同的情况?
如果你看看x86的各种乘法指令,只查看32位变量并忽略BMI2,你会发现:
-
imul r/m32
(32×32-> 64签名乘法) -
imul r32, r/m32
(32×32-> 32乘)* -
imul r32, r/m32, imm
(32×32-> 32乘法)* -
mul r/m32
(32×32-> 64无符号乘法)
请注意,只有“加宽”乘法具有无符号对应项。 中间标有星号的两种forms都是有符号和无符号乘法,因为对于没有得到额外“上部”的情况, 这是同样的事情 。
“扩展”乘法在C中没有直接等价,但编译器可以(并且经常)使用这些forms。
例如,如果你编译它:
uint32_t test(uint32_t a, uint32_t b) { return a * b; } int32_t test(int32_t a, int32_t b) { return a * b; }
使用GCC或其他一些相对合理的编译器,你会得到这样的东西:
test(unsigned int, unsigned int): mov eax, edi imul eax, esi ret test(int, int): mov eax, edi imul eax, esi ret
(带-O1的实际GCC输出)
因此,对于乘法(至少不是在C中使用的乘法类型)和其他一些操作,签名无关紧要,即:
- 加减
- 按位AND,OR,XOR,NOT
- 否定
- 左移
- 比较平等
x86不为那些提供单独的签名/未签名版本,因为无论如何都没有区别。
但是对于某些操作来说存在差异,例如:
- 师(
idiv
vsdiv
) - 余数(也是
idiv
vsdiv
) - 右移(
sar
vsshr
)(但要注意C中签名的右移) - 比较大于/小于
但最后一个是特殊的,x86没有单独的版本用于签名和无符号的这个,而是它有一个操作( cmp
,它实际上只是一个非破坏性的sub
)同时做两个,并给出几个结果(多个) “旗帜”中的位受影响)。 后来实际使用这些标志(分支,条件移动, setcc
)的setcc
然后选择他们关心的标志。 例如,
cmp a, b jg somewhere
如果a
“签名大于” b
将会去somewhere
。
cmp a, b jb somewhere
如果a
是“未签名的”,那么会去somewhere
。
有关标志和分支的更多信息,请参阅CMP后的汇编 – JG / JNLE / JL / JNGE 。
这不是签名和无符号乘法相同的正式certificate,我只是试着让你深入了解为什么它们应该是相同的。
考虑4位2的补码整数。 各个位的权重,从lsb到msb,1,2,4和-8。 当您将这些数字中的两个相乘时,您可以将其中一个数字分解为与其位对应的4个部分,例如:
0011 (decompose this one to keep it interesting) 0010 ---- * 0010 (from the bit with weight 1) 0100 (from the bit with weight 2, so shifted left 1) ---- + 0110
2 * 3 = 6所以一切都结账了。 这只是大多数人在学校学习的常规长乘法,只有二进制,这使得它更容易,因为你不必乘以十进制数,你只需要乘以0或1,然后移位。
无论如何,现在采取负数。 符号位的重量是-8,所以在某一点上你会得到一个部分乘积-8 * something
。 乘以8是左移3,所以前lsb现在是msb,所有其他位都是0.现在如果你否定它(毕竟它是-8,而不是8),没有任何反应。 零显然没有变化,但是8也是如此,并且通常只有msb设置的数字:
-1000 = ~1000 + 1 = 0111 + 1 = 1000
所以如果msb的权重为8(如无符号的情况下)而不是-8,那么你已经完成了同样的事情。
大多数现代处理器都支持有符号和无符号算术。 对于那些不受支持的算术,我们需要模拟算术。
引用X86架构的这个答案
首先,x86本身支持有符号数的二进制补码表示。 您可以使用其他表示forms,但这需要更多指令,通常会浪费处理器时间。
“原生支持”是什么意思? 基本上我的意思是你有一组用于无符号数字的指令和另一套用于有符号数字的指令。 无符号数字可以与有符号数字位于相同的寄存器中,实际上,您可以混合有符号和无符号指令,而无需担心处理器。 由编译器(或汇编程序员)来跟踪数字是否已签名,并使用适当的指令。
首先,二进制补码数的加法和减法与无符号数相同。 数字是正数还是负数没有区别。 (所以你只需要继续添加你的号码而不必担心。)
在比较时,差异开始显现。 x86有一种区分它们的简单方法:上面/下面表示无符号比较,大于/小于表示签名比较。 (例如JAE表示“如果大于或等于跳跃”且未签名。)
还有两组乘法和除法指令来处理有符号和无符号整数。
最后:如果你想检查溢出,你会对签名和无符号数做不同的处理。
cmp
和sub
一点补充。 我们知道cmp
被认为是非破坏性的sub
,所以让我们关注sub
。
例如,当x86 cpu执行sub
指令时
sub eax, ebx
如果eax或ebx的值是有符号还是无符号,cpu如何知道? 例如,考虑二进制补码中的4位宽度数:
eax: 0b0001 ebx: 0b1111
在有符号或无符号中,eax的值将被解释为1(dec)
,这很好。
但是,如果ebx是无符号的,它将被解释为15(dec)
,结果变为:
ebx:15(dec) - eax: 1(dec) = 14(dec) = 0b1110 (two's complement)
如果ebx已签名,则结果将变为:
ebx: -1(dec) - eax: 1(dec) = -2(dec) = 0b1110 (two's complement)
即使对于有符号或无符号,它们在二进制补码中的结果编码也是相同的: 0b1110
。
但有一个是正面的:14(dec),另一个是负面的:-2(dec),然后回到我们的问题:cpu如何判断哪个是哪个?
答案是cpu将评估两者,来自: http : //x86.renejeschke.de/html/file_module_x86_id_308.html
它评估有符号和无符号整数操作数的结果,并设置OF和CF标志,分别表示有符号或无符号结果中的溢出。 SF标志指示签名结果的符号。
对于此特定示例,当cpu看到结果: 0b1110
,它将SF标志设置为1
,因为如果0b1110
被解释为负数,则它为-2(dec)
。
然后,如果需要使用SF标志或者只是忽略它,它将取决于以下指令。