一尘不染

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

algorithm

C语言具有带符号和无符号类型,例如char和int。我不确定如何在汇编级别上实现它,例如,在我看来有符号和无符号的乘法会带来不同的结果,所以汇编同时执行无符号和有符号算术还是仅执行一个,这在某种程度上被仿真不同的情况?


阅读 299

收藏
2020-07-28

共1个答案

一尘不染

如果您查看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无符号乘法)

请注意,只有“加宽”乘法具有无符号的对应项。中间的两个带有星号的形式都是有符号和无符号乘法,因为对于这种情况,如果您没有多余的“上部”, 那是同一回事

“加宽”乘法在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不会为此提供单独的签名/未签名版本,因为无论如何都没有区别。

但是对于某些操作,则有所不同,例如:

  • 除(idivvs div
  • 余数(也idivvs div
  • 右移(sarvs shr)(但要注意C中有符号的右移)
  • 比较大于/小于

但是最后一个是特殊的,x86对此没有签名也没有签名的版本,而是有一个操作(cmp,实际上只是一个非破坏性的sub)同时执行,并且给出了多个结果(
“标记”受到影响)。稍后的说明会实际使用这些标志(分支,有条件的移动等setcc),然后选择它们关心的标志。例如

cmp a, b
jg somewhere

somewhere如果a“签名大于” 将继续b

cmp a, b
jb somewhere

somewhere如果a是“ unsigned below”,则将走b


这不是正式的证明有符号和无符号乘法是相同的,我将尽力让您了解为什么它们应该相同。

考虑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,则您将执行相同的操作。

2020-07-28