x86上的有符号和无符号算术实现

时间:2014-08-11 10:49:33

标签: c algorithm math x86 integer-arithmetic

C语言有签名和无符号类型,如char和int。 我不确定,它是如何在汇编级别实现的 例如,在我看来,有符号和无符号的乘法 会带来不同的结果,所以汇编都做无符号的 和签名算术或只有一个,这在某种程度上 模仿不同的情况?

3 个答案:

答案 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中使用的乘法类型)和其他一些操作,签名并不重要,即:

  • 加法和减法
  • 按位AND,OR,XOR,NOT
  • 否定
  • 左移
  • 比较平等

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)

cmpsub的一点补充。我们知道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标志或者只是忽略它,则它取决于以下指令。