C语言有签名和无符号类型,如char和int。 我不确定,它是如何在汇编级别实现的 例如,在我看来,有符号和无符号的乘法 会带来不同的结果,所以汇编都做无符号的 和签名算术或只有一个,这在某种程度上 模仿不同的情况?
答案 0 :(得分:11)
如果你看看x86的各种乘法指令,只查看32位变量并忽略BMI2,你会发现:
imul r/m32
(32x32-> 64签名乘法)imul r32, r/m32
(32x32-> 32倍增)* imul r32, r/m32, imm
(32x32-> 32倍增)* mul r/m32
(32x32-> 64无符号乘法)请注意,只有"扩大" multiply有一个无符号对应物。中间带有星号的两个表单都是有符号和无符号乘法,因为对于你没有得到额外的"上部",那个'同样的事情。
"扩大"乘法在C中没有直接的等价,但编译器可以(并经常)使用这些形式。
例如,如果你编译它:
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中使用的乘法类型)和其他一些操作,签名并不重要,即:
x86并没有为那些提供单独的签名/未签名版本,因为无论如何都没有区别。
但是对于某些操作来说存在差异,例如:
idiv
vs div
)idiv
vs div
)sar
vs shr
)(但要注意C中签名的右移)但最后一个是特殊的,x86也没有单独的签名和无符号版本,而是有一个操作(cmp
,这实际上只是一个非破坏性的sub
)同时进行两次,并给出几个结果(&#34中的多个位;标志"受影响)。后来实际使用这些标志的指令(分支,条件移动,setcc
)然后选择他们关心的标志。例如,
cmp a, b
jg somewhere
如果somewhere
是"签名大于"将a
b
。
cmp a, b
jb somewhere
somewhere
如果a
是"未签名," b
。
有关标志和分支的更多信息,请参阅Assembly - JG/JNLE/JL/JNGE after CMP。
这不能成为签名和无符号乘法相同的正式证明,我只是试着让你深入了解为什么它们应该是相同的。
考虑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,那么你已经完成了同样的事情。
答案 1 :(得分:4)
大多数现代处理器都支持有符号和无符号算术。 对于那些不受支持的算术,我们需要模拟算术。
从this answer引用X86架构
首先,x86本身支持这两个补码 签名号码的表示。您可以使用其他表示形式 但这需要更多的指示,通常是浪费 处理器时间。
我的意思是"原生支持"?基本上我的意思是有一个 用于无符号数字的指令集和另一组用于指定数字的指令 你用于签名号码。无符号数可以相同 注册为签名号码,实际上你可以混合签名和 未签名的指令,无需担心处理器。它取决于 编译器(或汇编程序员)来跟踪数字是否是 是否签名,并使用适当的说明。
首先,两个补码数具有加和的性质 减法与无符号数相同。它没有 数字是正数还是负数的差异。 (所以你只是 继续,无需担心地添加和提交您的号码。)
在进行比较时,差异开始显现。 x86有一个 区分它们的简单方法:上/下表示无符号 比较和大于/小于表示签名比较。 (例如。 JAE表示"如果高于或等于跳跃#34;并且是未签名的。)
还有两组乘法和除法指令 处理有符号和无符号整数。
最后:如果你想检查,比如溢出,你会这样做 签名和无符号数字的区别不同。
答案 2 :(得分:0)
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
,因为它-2(dec)
如果0b1110
被解释为负数。
如果需要使用SF标志或者只是忽略它,则它取决于以下指令。