【问题标题】:what's the point using unsigned int in C?在 C 中使用 unsigned int 有什么意义?
【发布时间】:2018-12-22 22:54:55
【问题描述】:

我认为 unsigned int 只能存储 >= 0 的整数。 但是我尝试将负数分配给无符号整数,没有发生任何特别的事情。 它似乎没有问题地存储了值。

那么signed int和unsigned int有什么区别,如果它无论如何都可以存储任何值,那又有什么意义呢?

【问题讨论】:

  • 提示:默认情况下,大多数 C 编译器都非常谨慎地提供未经请求的警告。让它说出来,你会感到震惊。
  • 存储的值没有问题”:有问题。
  • 重复答案???:stackoverflow.com/questions/5169692/…
  • 试试这个:int main(){ unsigned int a = -1; if( 2U < a ){ printf("2 < '-1'\n"); }}
  • 如果您提供声称显示无符号整数存储负数的代码,我们将向您指出它依赖于未定义行为的地方,否则根本不会显示您声称的内容.

标签: c int unsigned signed


【解决方案1】:

类似的语句

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

在 C 中完全合法且定义明确。负值在分配给无符号整数类型时会被隐式转换(例如,参见 this 在线 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 始终为真。

【讨论】:

    【解决方案2】:

    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 标准允许并支持使用不同整数编码的机器,因此可移植代码不应该假设二进制表示将在这样的隐式转换后保留。

    希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      在 C 中使用 unsigned int 的要点是:

      • 它为您提供更多正值范围(有符号至少 32,767,无符号至少 65,535)
      • 它使您能够使用数字进行屏蔽,并在对数字进行位移时避免未定义的行为
      • 它让编译器为您检查您没有为数字分配不正确的值(如果您知道它应该是无符号的),如果您在打开警告的情况下进行编译,就会发生这种情况。

      【讨论】:

      • @Fureeish,2s 补码是表示 signed 数字的约定。根据定义,无符号类型的表示使用它。根据 C,无符号数的表示没有符号位。另一方面,这并没有给出更大范围的数字,或者更多的数字,而是一个不同的数字范围,以及更多的正数。
      • 只回答提到有符号整数中未定义的位移行为。
      • @JohnBollinger 在"and more positive ones" 上点赞。像unsigned long 这样的无符号类型的正数范围允许相同。示例:ULONG_MAX == LONG_MAX,尽管 ULONG_MAX/2 == LONG_MAXfar far 更常见。多年来我还没有看到一个平台使用过这个古老的特性,因为它意味着一个填充的无符号类型,而且肯定永远不会再看到了。
      • 这是我第一次听说这样的实现,@chux。有趣的。我看到标准允许它,但前提是签名类型的范围大于该类型所需的最小范围。但是由于它与手头的问题有关,即使是最大可表示值不大于相应有符号类型的无符号类型,也可以对其行为做出相应有符号类型所没有的一些保证(我知道你知道) .
      • @JohnBollinger 我遇到的奇异事件是比 32 位更宽的类型,实际上是一些有符号的 intN_t 和 (N-1) 位无符号,因为“符号”位是填充位。它与本机支持签名的*,/ 的处理器有关,但不是未签名的。今天,这样的模型会受到用户社区的太多反对,鉴于我们今天没有看到它意味着达尔文的压力将其降级为计算机墓地。因此,即使允许,也不是实际问题,例如独角兽26-bit float
      【解决方案4】:

      一个重要的点是溢出有符号整数是未定义的行为,而无符号整数被定义为环绕。事实上,当您将负值分配给一个时,就会发生这种情况:它只是简单地环绕直到值在范围内。

      虽然无符号类型的这种环绕行为意味着为它们分配负值确实是完全有效的,但将它们转换回有符号类型并没有那么明确(充其量是实现定义的,最坏的情况是未定义的行为,取决于你是怎么做的)。尽管在许多常见平台上,有符号整数和无符号整数在内部可能是相同的,但值的预期含义对于比较、转换(例如浮点数)以及编译器优化都很重要。

      总之,当您需要为上溢和下溢定义明确的环绕语义和/或您需要表示大于相应(或最大合适)有符号的最大值的正整数时,您应该使用无符号类型类型。从技术上讲,在大多数情况下,您可以通过在无符号类型之上实现负数来避免 signed 类型(毕竟,您可以简单地选择将某些位模式解释为负数),但是……为什么,当语言“免费”提供这项服务。 C 中带符号整数的唯一真正问题是必须注意溢出,但作为回报,您可能会得到更好的优化。

      【讨论】:

        【解决方案5】:

        无符号具有 1) 更高的最大值和 2) 已定义的环绕溢出。

        如果精度无限

         (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 在概念上被添加到值中)。

        另一方面,对于带符号的类型,溢出是未定义的,这意味着如果您允许它发生,您的程序将不再具有良好的格式,并且标准对其行为不做任何保证。编译器exploit this for optimization 假设它永远不会发生。

        例如,如果你编译

        #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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-12
          • 2021-08-07
          • 1970-01-01
          • 2013-02-01
          • 2010-10-04
          • 1970-01-01
          相关资源
          最近更新 更多