什么时候整数的符号真的重要?

时间:2011-11-07 17:15:24

标签: c signedness

由于在C中定义转换和操作的方式,使用有符号或无符号变量似乎很少:

uint8_t u; int8_t i;

u = -3;    i = -3;
u *= 2;    i *= 2;
u += 15;   i += 15;
u >>= 2;   i >>= 2;

printf("%u",u); // -> 2
printf("%u",i); // -> 2

那么,是否有一套规则可以告诉变量的符号在哪些条件下确实有所作为?

6 个答案:

答案 0 :(得分:9)

在这些情况下很重要:

  • 除法和模数:-2/2 = 1-2u/2 = UINT_MAX/2-1-3%4 = -3-3u%4 = 1
  • 移。对于负的有符号值,>><<的结果是实现定义或未定义的,resp。对于无符号值,始终定义它们。
  • 关系-2 < 0-2u > 0
  • 溢出。编译器可假定x+1 > x始终为真 iff x具有签名类型。

答案 1 :(得分:8)

是。签名将影响C中的大于和小于运算符的结果。请考虑以下代码:

unsigned int a = -5;
unsigned int b = 7;

if (a < b)
    printf("Less");
else
    printf("More");

在此示例中,“More”输出不正确,因为编译器将-5转换为非常高的正数。

这也会影响您使用不同大小的变量进行算术运算。再次考虑这个例子:

unsigned char a = -5;
signed short b = 12;

printf("%d", a+b);

返回的结果是 263,不是预期的7.这是因为-5实际上被编译器视为251。溢出使您的操作对于相同大小的变量正常工作,但是在扩展时,编译器不会扩展无符号变量的符号位,因此它将它们视为在较大空间中的原始正表示。研究two's compliment的工作原理,你会看到这个结果的来源。

答案 2 :(得分:6)

它会影响您可以存储在变量中的值范围。

答案 3 :(得分:3)

主要是相关的。

printf("%d", (u-3) < 0); // -> 0
printf("%d", (i-3) < 0); // -> 1

答案 4 :(得分:3)

无符号整数上的溢出只是环绕。在签名值上,这是未定义的行为,一切都会发生。

答案 5 :(得分:2)

2的补数的签名只是你解释数字的问题。想象一下3位数字:

000
001
010
011
100
101
110
111

如果你认为000为零,而数字对于人类来说是自然的,你可以这样解释:

000: 0
001: 1
010: 2
011: 3
100: 4
101: 5
110: 6
111: 7

这称为“无符号整数”。您将所有内容视为大于/等于零的数字。

现在,如果你想让一些数字为负数怎么办?好吧,2的补充来救援。大多数人都知道2的补码只是一个公式,但实际上它只是一致的模2 ^ n其中n是你数字中的位数。

让我举几个一致性的例子:

2 = 5 = 8 = -1 = -4 module 3
-2 = 6 = 14 module 8

现在,为了方便起见,我们假设您决定将数字的最左边一位作为其符号。所以你想拥有:

000: 0
001: positive
010: positive
011: positive
100: negative
101: negative
110: negative
111: negative

查看您的数字全等模2 ^ 3(= 8),您知道:

4 = -4
5 = -3
6 = -2
7 = -1

因此,您将数字视为:

000: 0
001: 1
010: 2
011: 3
100: -4
101: -3
110: -2
111: -1

如您所见,-3和5(例如)的实际位是相同的(如果数字有3位)。因此,撰写x = -3x = 5可以获得相同的结果。

解释数字全等模2 ^ n还有其他好处。如果你总结2个数字,一个负数和一个正数,它可能会在纸上发生,你有一个被丢弃的进位,但结果仍然是正确的。为什么?那个进位是2 ^ n,它与0 modulo 2 ^ n一致!这不方便吗?

溢出也是一致性的另一种情况。在我们的例子中,如果你将两个无符号数字5和6相加,你得到3,实际上是11。

那么,为什么要使用signed和unsigned?对于CPU来说,实际上差别很小。但是

  • 如果数字有n位,则无符号表示0到2 ^ n-1
  • 之间的数字
  • 如果数字有n位,则signed表示从-2 ^(n-1)到2 ^(n-1)-1
  • 的数字

因此,例如,如果将-1分配给无符号数,则与为其分配2 ^ n-1相同。

根据您的示例,这正是您正在做的事情。你正在为一个uint8_t分配-3,这是非法的,但就CPU而言,你正在为它分配253。然后所有其余的操作对于两种类型都是相同的,并且最终得到相同的结果。

但有一点是你的例子未命中。在签名号码上的运营商>>在转移时扩展了标志。由于你的两个操作的结果在转移前都是9,你不会注意到这一点。如果您没有+15,那么i中有-6,u中有250,>> 2 -2会导致i (如果使用%u,254打印)和u中的62。 (有关一些技术细节,请参阅下面的Peter Cordes的评论)

为了更好地理解这一点,请以此为例:

  (signed)101011 (-21) >> 3 ----> 111101 (-3)
(unsigned)101011 ( 43) >> 3 ----> 000101 ( 5)

如果你注意到,楼层(-21/8)实际上是-3而楼层(43/8)是5.但是,-3和5不相等(并且不是全等模64(64因为有6)比特))