在C语言中使用无符号整型的意义是什么?

10

我曾认为unsigned int只能存储大于等于0的整数。

但是当我尝试将负数赋值给unsigned int时,似乎没有发生任何特殊情况。

看上去它毫无问题地存储了该值。

那么signed和unsigned int有什么区别呢?既然unsigned int可以存储任何值,那有什么意义呢?


2
提示:默认情况下,大多数C编译器都不太愿意提供未经请求的警告。让它说话,你会感到惊讶。 - Deduplicator
5
“_stored the value with no problem_”原本意思是“储存数值没有问题”,但实际上存在问题。 - DYZ
1
尝试这个:int main(){ unsigned int a = -1; if( 2U < a ){ printf("2 < '-1'\n"); }} - datenwolf
1
如果您提供了一段代表无符号整数存储负数的代码,我们将会指出代码中存在未定义行为的地方,或者仅仅是无法展示您所声称的内容。 - John Bollinger
差异会带来很多影响,例如整数提升有符号溢出导致未定义行为 - Shafik Yaghmour
显示剩余3条评论
5个回答

9

像这样的语句

unsigned int t = -1;
printf("%u", t);

在C语言中,将负值赋给无符号整型变量是完全合法且明确定义的。当这样的操作发生时,会自动进行类型转换(详见例如在线C标准草案):

6.3.1.3 有符号和无符号整数

(2) 否则,如果新类型是无符号的,则通过重复加上或减去一个比新类型中最大可表示值还大的值,直到该值在新类型的范围内为止,对该值进行转换。

以上程序的输出是一个无符号值。
4294967295

您可以将“负”值分配给无符号整数类型,但其实际意义并不是负值。当您将无符号整数值与负值进行比较时,这尤其重要。例如,请考虑以下两个循环:

int i = 10;
while (--i >= 0) {  // 10 iterations
    printf("i: %d\n", i);
}

unsigned int u = 10;
while (--u >= 0) {  // endless loop; warning provided.
    printf("u: %u\n", u);
}

第一个将在10个迭代后结束,而第二个永远不会结束: 无符号整数值不能变为负数,因此u >= 0始终为真。

3
在C语言中使用unsigned int的优点是:
  • 它可以提供更大的正数值范围(有符号最少32,767,无符号最少65,535)
  • 它允许您在位移数字时使用该数字进行掩码操作,避免未定义的行为
  • 如果你知道它应该是无符号的,它可以让编译器为你检查是否将不适当的值赋给了该数字,这也是如果你打开警告编译时会发生的情况。

3
@Fureeish,2的补码是表示有符号数字的约定。根据定义,因此,无符号类型的表示不使用它。根据C语言,无符号数字的表示不具有符号位。另一方面,这并没有给出更大范围的数字或更多的数字,而是一个不同的数字范围,和更多的正数。 - John Bollinger
1
只有提到有符号整数位移行为未定义的答案。 - fdk1342
@JohnBollinger 对于“更正面的”观点提出了很好的意见。像unsigned long这样的无符号类型的正数范围是被允许相同的。例如,ULONG_MAX == LONG_MAX,即使ULONG_MAX/2 == LONG_MAX更为常见。我已经很多年没有看到过任何平台使用这个过时的特性,因为它意味着一个有填充的无符号类型,并且肯定不会再见到。 - chux - Reinstate Monica
这是我第一次听说这样的实现,@chux。有趣。我看到标准允许它,但前提是有符号类型的范围大于该类型所需的最小范围。但就与手头的问题相关而言,即使无符号类型的最大可表示值不大于相应有符号类型的值,它也享有一些关于其行为的保证,而相应的有符号类型则没有(我知道你已经意识到这一点)。 - John Bollinger
1
@JohnBollinger 我遇到的唯一情况是一个比32位类型更宽的类型,实际上是一些有符号的intN_t和一个(N-1)位无符号数,因为"符号"位是填充位。这与处理器本地支持有符号的*,/有关,但不支持无符号的。今天,这样的模型会受到用户社区的太多反对,而且我们今天没有看到它,这意味着达尔文压力将其归入了计算机坟墓。所以即使被允许,也不是一个实际的问题,就像一只独角兽26位浮点数 - chux - Reinstate Monica

3
重要的一点是,溢出有符号整数是未定义行为,而无符号整数被定义为循环。实际上,当您将负值分配给一个无符号整数时,它只是简单地循环,直到该值在范围内。
虽然无符号类型的这种循环行为意味着向它们分配负值确实是完全有效的,但将它们转换回有符号类型并不像定义得那么好(在最好的情况下,它是实现定义的,在最坏的情况下是未定义行为,具体取决于您如何做)。虽然在许多常见平台上,有符号和无符号整数在内部可能是相同的,但对于比较、转换(例如浮点数)以及编译器优化,值的预期含义都很重要。
总之,当您需要明确定义的溢出和下溢的循环语义以及/或者需要表示大于相应的(或最大适当的)有符号类型的正整数时,应使用无符号类型。从技术上讲,您可以通过在无符号类型之上实现负数(毕竟,您可以选择将某些位模式解释为负数)来在大多数情况下避免使用有符号类型,但是...为什么这样做,当语言提供了这项“免费”的服务。在C中,有符号整数唯一的真正问题是要注意溢出,但作为回报,您可能会获得更好的优化。

3
您说得对,unsigned int只能存储大于等于0的整数。(当然,还有一个上限,这个上限取决于您的架构,并在limits.h中定义为UINT_MAX。)
通过将带符号的int值赋给unsigned int,您正在调用隐式类型转换。C语言对此有一些非常精确的规则。尽可能保留值是编译器的首要任务。例如:
int x = 5;
unsigned int y;

y = x;

上述代码也进行了类型转换,但由于值“5”可在有符号和无符号整数范围内表示,因此该值可以被保留,因此 y 也将具有值为 5。
现在考虑以下代码:
x = -5;
y = x;

具体来说,在这种情况下,您正在分配一个值,该值不在可表示的 unsigned int 范围内,因此编译器必须将该值转换为范围内的某个值。C 标准规定,将值 1 + UINT_MAX 加到该值上,直到它在 unsigned int 范围内。在大多数系统上,UINT_MAX 定义为 4294967925(2^32 - 1),因此 y 的值实际上将是 4294967921(或十六进制中的 0xFFFFFFFB)。
需要注意的是,在二进制补码机器上(现在几乎普遍使用),signed int 值为 -5 的二进制表示也是 0xFFFFFFFB,但这不是必需的。C 标准允许并支持使用不同整数编码的机器,因此可移植代码不应假定在这种隐式转换后二进制表示将被保留。
希望这有所帮助!

1
无符号数具有更高的最大值和定义的环绕溢出。如果使用无限精度,
 (unxigned_c = unsigned_a + unsinged_b) >= UINT_MAX

然后unsigned_c将会对UINT_MAX+1取模:

#include <limits.h>
#include <stdio.h>
int main()
{
    printf("%u\n", UINT_MAX+1); //prints 0
    printf("%u\n", UINT_MAX+2); //prints 1
    printf("%u\n", UINT_MAX+3); //prints 2
}

当你把有符号的值存储到无符号中时,类似的情况会发生。在这种情况下,适用于6.3.1.3p2 -- 概念上将UINT_MAX+1添加到该值中。

而对于有符号类型,溢出是未定义的,这意味着如果你允许它发生,你的程序就不再是良构的,标准对其行为不做任何保证。编译器利用此进行优化,假设它永远不会发生。

例如,如果你编译

#include <limits.h>
#include <stdio.h>

__attribute__((noinline,noclone)) //or skip the attr & define it in another tu
_Bool a_plus1_gt_b(int a, int b) { return a + 1 > b; }

int main()
{
    printf("%d\n", a_plus1_gt_b(INT_MAX,0)); //0
    printf("%d\n", INT_MAX+1); //1
}

在使用-O3的gcc编译时,很可能会打印出以下内容。
1
-2147483648

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接