【问题标题】:Unsigned integers无符号整数
【发布时间】:2022-01-04 08:20:45
【问题描述】:

谁能帮我理解有符号/无符号整数和有符号/无符号字符之间的区别?在这种情况下,如果它是无符号的,值不会永远不会达到负数并继续 0 的无限循环吗?

 int main()
{
   unsigned int n=3;
   while (n>=0)
   {
       printf ("%d",n);
       n=n-1;
   }

    return 0;
}

【问题讨论】:

  • 我建议您在 SO stackoverflow.com/a/5789914 中查看此答案
  • 另外,建议您在 print 语句之后放置一个 sleep(),以便您可以按照顺序进行操作。
  • 这是一个棘手的问题。是的,它会无限期地运行,但不是因为 n 保持为 0。如果你真的运行代码,你会发现 print() 会打印“3210-1-2-3-4-5...”,因为你给了它格式将n解释为int。事实上,您可以使用 printf ("%u",n); 之类的东西来打印价值。您将有一次 0,但在下一次迭代中会发生无符号下溢。如果int 是4 个字节长,那么0-1 = 0xFFFFFFFF。那是十进制格式的 4294967295。
  • @PavanDittakavi 你到底为什么要在这个问题中添加 C++17 标签?
  • %d 是无符号整数的错误printf 格式化字符串,这是未定义的行为。虽然在实践中你会看到你的 unsigned int 被解释为一个有符号的 int。

标签: c loops unsigned


【解决方案1】:

值不会永远达到负数

正确,不可能是负数。

然后在 0 的无限循环中继续

不,它会从零环绕到 unsigned int 的最大值,这是明确定义的行为。如果您使用正确的转换说明符 %u 而不是不正确的 %d,您会注意到以下输出:

3
2
1
0
4294967295
4294967294
...

【讨论】:

  • 是的,但这是我的教授给我的一个例子,用 %d 而不是 %u。我的问题是,如果它永远不能存储负值,为什么输出会无限打印负数?
  • @Nati 用%d 打印无符号数字严格来说是未定义的行为。您将观察到的可能是一些尝试将无符号数转换为有符号等价物(这不是保证行为,但实际上可能会发生这种情况)。但这是printf内部发生的事情,它不会影响变量的实际值
【解决方案2】:

有符号和无符号这两个术语是指 CPU 如何处理位序列。

这里有两点需要了解:

  1. CPU 如何将有限位序列添加到单个有限结果中
  2. CPU 如何区分有符号和无符号操作数

让我们从 (1) 开始。

我们以 4 位半字节为例。
如果我们要求 CPU 添加00010001,结果应该是2,或者0010。 但是如果我们要求它加上11110001,结果应该是16,或者10000。但它只有 4 位来包含结果。惯例是环绕,或循环,回到0,有效地忽略Most Significant Bit。另见integer overflow.

为什么这是相关的?因为它产生了一个有趣的结果。也就是说,根据上面的定义,如果我们让x = 1111,那么我们得到x + 1 = 0。好吧,x1111 现在看起来和行为都非常像 -1。这就是有符号数和运算的诞生。而如果1111可以被认为是-1,那么1111 - 1 = 1110应该是-2,以此类推。

现在让我们看看(2)。

当 C 编译器看到您定义了 unsigned int 时,它将使用特殊的 CPU 指令来处理它认为相关的无符号数。例如,这与jump 指令相关,CPU 需要知道您的意思是它向前跳跃,还是稍微向后。为此,它需要知道您的操作数是否以有符号或无符号方式解释。

另一方面,将两个数字相加的操作从根本上忽略了后续解释。唯一的一点是CPU会在加法操作后打开一个特殊的标志,告诉你是否发生了回绕,供你自己审计。

但重要的是要理解位的顺序不会改变;只有它的解释。

为了将所有这些与您的示例联系起来,从未签名的 0 中减去 1 将简单地回绕回 1111,在您的情况下为 2^32。

最后,有符号/无符号还有其他用途。例如,由于它被定义为不同的类型,这允许编写定义合约的函数,其中只有无符号整数,比方说,可以传递给它。此外,当您想要显示或打印数字时,它也很重要。

【讨论】:

  • C 语言为无符号整数定义了 wrap-around,而为有符号整数定义了 overflow。这与 CPU 关系不大,只是语言的定义方式。
  • @Lundin 总的来说,我会同意并关注语言本身的定义,因为不同的语言确实可以并且确实可以任意定义数字运算。在这种情况下,我认为这种方法更好,因为 1)C 从一开始就非常面向机器,并且 2)OP 在这个级别上似乎不理解这些概念,所以有一个空白需要填补。
【解决方案3】:

有符号数表示是正整数和负整数的分类,而无符号分类是正整数و的分类,您编写的代码将永远运行,因为 n 是无符号数并且始终表示正数。

【讨论】:

    【解决方案4】:

    两件重要的事情:

    在一个层面上,常规 signedunsigned 值之间的差异只是我们解释位的方式。如果我们将自己限制为 3 位,我们有:

    bits signed unsigned
    000 0 0
    001 1 1
    010 2 2
    011 3 3
    100 -4 4
    101 -3 5
    110 -2 6
    111 -1 7

    位模式不会改变,只是解释问题是我们让它们表示从 0 到 2N-1 的非负整数,还是从 -2N/ 的有符号整数2 到 2N/2-1.

    另一件重要的事情是在一个类型上定义了哪些操作。对于无符号类型,加法和减法被定义为从 0 到 2N-1 “环绕”。但是对于有符号类型,上溢和下溢是未定义的。 (在某些机器上它们会环绕,但不是全部。)

    最后,还有正确匹配printf 格式的问题。对于%d,您应该给它一个有符号 整数。但是你给了它unsigned。严格来说,这也会导致未定义的行为,但在这种情况下(并不太令人惊讶),发生的事情是它采用相同的位模式并将其打印出来,就好像它是有符号的,而不是无符号的。

    【讨论】:

      【解决方案5】:

      在这种情况下,如果它是无符号的,值不会永远不会达到负数...?

      你是对的。但是在声明 printf ("%d",n);“欺骗” printf() 函数 - 使用 type conversion specifier d - 变量 n 中的数字是 签名。

      改用类型转换说明符uprintf ("%u",n);

      ...永远不会达到负数并在 0 的无限循环中继续?

      没有。 “永远不会达到负数”“停在0并拒绝进一步递减”不同。

      其他人已经解释过了。这是我的解释,以类比的形式:

      • 想象自己是一个永无止境的非负整数序列:

        ..., 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ...  // the biggest is 3 only for simplicity
        
      • ——或模拟时钟上的数字:

                          

      你可以不断地增加/减少一个数字,循环往复。

      【讨论】:

        猜你喜欢
        • 2010-09-19
        • 2013-10-02
        • 1970-01-01
        • 2018-08-18
        • 1970-01-01
        • 2015-02-17
        • 2015-08-13
        • 2013-08-29
        • 1970-01-01
        相关资源
        最近更新 更多