当前位置: 首页 > 知识库问答 >
问题:

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

锺离昂然
2023-03-14

C语言有符号和无符号类型,如char和int。我不确定它是如何在程序集级别实现的,例如,在我看来,有符号和无符号的乘法会带来不同的结果,那么程序集是同时做无符号和有符号的算术,还是只做一个,这在某种程度上是针对不同情况模拟的?

共有3个答案

佘茂才
2023-03-14

对< code>cmp和< code>sub的一点补充。我们知道< code>cmp被认为是非破坏性的< code>sub,所以让我们把重点放在< code>sub上。

例如,当 x86 cpu 执行指令时,

sub eax, ebx

cpu如何知道eax或ebx的值是有符号的还是无符号的?例如,考虑两个补码中的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)

即使对于有符号或无符号,它们的结果在二进制补码中的编码是相同的:< code>0b1110。

但其中一个是正的:14(dec),另一个是负的:-2(dec)。然后回到我们的问题:cpu如何告诉哪个是哪个?

答案是cpu将评估两者,来自:http://x86.renejeschke.de/html/file_module_x86_id_308.html

它计算有符号和无符号整数操作数的结果,并设置OF和CF标志以分别指示有符号或无符号结果中的溢出。SF标志指示有符号结果的符号。

对于这个具体的例子,当cpu看到结果:< code>0b1110时,它会将SF标志设置为< code>1,因为如果< code>0b1110被解释为负数,它就是< code>-2(dec)。

那么这取决于下面的指令,如果他们需要使用SF标志或干脆忽略它。

薛涛
2023-03-14

大多数现代处理器支持有符号和无符号算术。对于那些不被支持的算法,我们需要对其进行仿真。

引用X86架构的这个答案

首先,x86 对两者的有符号数补码表示具有本机支持。您可以使用其他表示形式,但这需要更多的指令,并且通常会浪费处理器时间。

“原生支持”是什么意思?基本上,我的意思是有一组指令用于无符号数字,另一组用于有符号数字。无符号数字可以与有符号数字位于相同的寄存器中,实际上您可以混合有符号和无符号指令,而不必担心处理器。编译器(或汇编程序员)负责跟踪数字是否签名,并使用适当的说明。

首先,二进制补数具有加法和减法与无符号数相同的性质。这些数字是正数还是负数没有什么区别。(所以你只要继续添加和SUB你的数字,不用担心。)

x86有一个简单的区分方法:高于/低于表示无符号比较,大于/小于表示有符号比较。(例如,JAE表示“如果高于或等于跳转”,并且是无符号的。)

还有两组乘法和除法指令用于处理有符号和无符号整数。

最后:如果要检查溢出,则可以对有符号和无符号数字进行不同的检查。

叶俊郎
2023-03-14

如果您查看x86的各种乘法指令,只查看32位变体,忽略BMI2,您会发现:

  • imul r/m32(32x32-

请注意,只有“加宽”乘法才有无符号对应项。中间的两种形式,标有星号,既是有符号的,也是无符号的乘法,因为对于你没有得到额外的“上半部分”的情况,这是一回事。

“加宽”乘法在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;
}

使用html" target="_blank">GCC或其他相对合理的编译器,您会得到这样的结果:

test(unsigned int, unsigned int):
    mov eax, edi
    imul    eax, esi
    ret
test(int, int):
    mov eax, edi
    imul    eax, esi
    ret

(实际GCC输出,带-O1)

因此,符号性对于乘法(至少对于您在C中使用的乘法类型)和其他一些运算来说并不重要,即:

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

x86不提供单独的有符号/无符号版本,因为无论如何没有区别。

但对于某些操作有区别,例如:

  • 除法(积分迪夫
  • 余数(也包括积分迪夫
  • 右移(sarshr)(但要注意 C 中的签名右移)
  • 比较大于/小于

但最后一个很特别,x86也没有单独的有符号和无符号版本,相反,它有一个操作(cmp,它实际上只是一个无损的sub),它同时执行这两个操作,并给出多个结果(“标志”中的多位受到影响)。后来实际使用这些标志的指令(分支、条件移动、setcc)然后选择他们关心的标志。例如,

cmp a, b
jg somewhere

如果< code>a的“符号大于”< code>b,将进入< code >某处。

cmp a, b
jb somewhere

如果< code>a是“在< code>b下面无符号的”,将进入< code >某处。

有关标志和分支的更多信息,请参阅CMP之后的Assembly-JG/JNLE/JL/JNGE。

这不会是有符号乘法和无符号乘法是相同的正式证明,我只是试图让你深入了解为什么它们应该是相同的。

考虑4位二进制补码整数。它们各个位的权重从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,因此在某一点上,您将得到部分乘积< code>-8 * something。乘以8就是左移3,所以之前的lsb现在是msb,所有其他位都是0。现在如果你否定它(毕竟是-8,不是8),什么也不会发生。很明显,零是不变的,但8也是不变的,一般来说,只有msb集的数字:

-1000 = ~1000 + 1 = 0111 + 1 = 1000

因此,如果msb的权重是8(在无符号情况下),而不是-8,那么您将执行相同的操作。

 类似资料:
  • 我正在设计一个简单的玩具指令集和附带的仿真器,并试图找出支持什么指令。在算术方面,我目前有无符号加法、减法、乘法和除法。然而,对于以下问题,我似乎找不到一个明确的答案:哪种算术运算符需要有符号版本,而无符号和二的补码有符号版本对哪一种是等价的? 例如,1111在2的补码中等于-1。如果你给它加1,假装它是一个无符号的数字,你会得到0000,即使把它想象成-1也是正确的。然而,这适用于所有数字吗?其

  • 我认为2补码的全部意义在于可以以相同的方式实现有符号和无符号数字的操作。维基百科甚至特别将乘法列为受益的操作之一。那么为什么x86对每个都有单独的说明,和?x86-64仍然如此吗?

  • 我正在使用MASM 14.0进行组装,我与下面代码的输出混淆。 这两个算术运算都是在无符号整数255和127上完成的。 然而,CPU将第一个操作255视为无符号整数,并设置进位标志,当将1添加到无符号255时会出现这种情况。 完整的状态标志是CF=1 SF=0 ZF=1 OF=0 AF=1 PF=1,eax为0 但是,第二个操作在设置溢出标志时将127视为有符号整数,如果将127加1,则会发生溢出

  • 我在看的书:CS-app 2。c有无符号和有符号的int类型,并且在大多数架构中使用二进制补码算法来实现有符号值;但是学了一些汇编代码之后,发现很少有指令区分无符号和有符号。所以我的问题是: > 区分有符号和无符号是编译器的责任吗?如果是,它是如何做到的? 谁实现两个补码算法——CPU还是编译器? 添加更多信息: 在学习了更多的指令后,实际上有一些指令区分有符号和无符号,例如setg、seta等。

  • 有符号和无符号变量在按位运算上有区别吗?< br >例如,在处理无符号数字时:< br> 将得到00000101。 但当处理带符号的数字时会发生什么?

  • 精明的加法学专家会注意到,它只能加到62位。我在编译器和芯片设计方面的经历告诉我,保留位值黄金。所以我们有两个(设置为零)。 那么这是否意味着: 问题一: ~表示36位移位,包含10位类型Id和其余36位本地Id: #00000000000000000000# ShardID 3429的 二进制=1101 0110 0101 因此(hashedValue>>46)=00000 0 110 1 01