【问题标题】:What is signed integer overflow?什么是有符号整数溢出?
【发布时间】:2017-10-17 12:04:18
【问题描述】:

我正在从 CS50 学习 C。当我运行我的代码时,它显示“有符号整数溢出”。

#include <stdio.h>
#include <cs50.h>

int main(void)
{
    int x = 41;
    int c = 0;
    while(x>=25)
    {
      c = c+1;
    }

    printf("%i\n", c);
}

谁能解释一下这是什么意思?

【问题讨论】:

  • 确实知道你有一个无限循环吗?
  • c 无限递增,因为 while 循环永远不会结束。 x 永远不会递减,因此始终保持在 41,并且 41 始终大于 25。
  • 您将永远添加到 c 中。它只能保持如此高的价值。
  • 至于 overflow 问题。假设您有一个signed char,其值范围为-128127(使用two's complement)。现在如果signed char 类型的变量的值是127,并且您添加1,那么会发生什么?什么是新价值?你不知道,因为你有一个有符号整数溢出,导致undefined behavior。问题与int(实际上是signed int)相同,只是值更大。
  • 只需将x = x -1; 添加到您的while 中;)

标签: c cs50


【解决方案1】:

您的 while 条件将始终为真,这意味着循环将永远运行,在每次迭代中将 1 添加到 c

由于c 是 (signed) int,这意味着它将缓慢增加到最大值,之后下一个增量将是 UB(未定义行为)。许多机器在这个特定的 UB 中所做的就是将c 转为负数,我想这不是你想要的。这是由于一种称为“有符号整数溢出”的现象而发生的。

让我们假设 32 位 int 并使用 二的补码signed int 在二进制 sign bit (0 for positive, 1 for negative) | 31 bits 中看起来像这样。零看起来像000...00,一看起来像000...01等等。

Max signed int 看起来像0111...11 (2,147,483,647)。当向这个数字加 1 时,您将得到 100...000,它翻转了符号位,现在将产生一个负数。添加另一个 1 将导致 100...001 再次具有符号位,这意味着它仍然是负数...

c 声明为无符号将确保c 保持非负数。此外,以while(x-- &gt;= 25) 结束循环也是一个好主意:)

【讨论】:

  • c 的值没有变为负数。它很可能会在现实中发生,但从技术上讲,它是未定义的行为,因此几乎任何事情都可能发生。
  • @Someprogrammerdude 即使在二进制补码假设下?修好了……
  • 是的,单数整数溢出或下溢是 C 规范中的 UB。这必须是因为它不能假设任何特殊的方法来编码负数。但实际上,常见的二进制补码编码会使溢出时为负数。
  • @Someprogrammerdude 您所描述的将是“实现定义的行为”。未定义的行为意味着允许编译器的优化器假设它永远不会发生并基于该假设重写您的代码。 (例如,请参阅this。)
【解决方案2】:

“有符号整数溢出”意味着您尝试存储的值超出了该类型可以表示的值范围,并且该操作的结果是 undefined(在这种特殊情况下,您的程序因错误而停止)。

由于您的 while 循环永远不会终止(x &gt;= 25 的计算结果为 true,并且您永远不会更改 x 的值),因此您不断将 1 加到 c 直到您达到一个超出有符号范围的值int可以代表。

请记住,在 C 中,整数和浮点类型具有固定大小,这意味着它们只能表示固定数量的值。例如,假设int 是 3 位宽,这意味着它只能存储 8 个不同的值。这些值是什么取决于位模式的解释方式。您可以存储“无符号”(非负)值[0..7],或“有符号”(负和非负)值[-3...3][-4..3],具体取决于表示形式。以下是解释三位值的几种不同方式:

Bits    Unsigned    Sign-Magnitude    1's Complement    2's Complement
----    --------    -------------     --------------    -------------- 
 000           0                0                  0                 0
 001           1                1                  1                 1
 010           2                2                  2                 2
 011           3                3                  3                 3
 100           4               -0                 -3                -4
 101           5               -1                 -2                -3
 110           6               -2                 -1                -2
 111           7               -3                 -0                -1

大多数系统对有符号整数值使用 2 的补码。是的,符号幅度和 1 的补码对零有正负表示。

所以,假设 c 是我们的 3 位签名 int。我们从0 开始,每次循环添加1。在c3 之前,一切都很好 - 使用我们的 3 位符号表示,我们无法表示值 4。操作的结果是未定义的行为,这意味着编译器不需要以任何特定方式处理问题。从逻辑上讲,您希望该值根据使用的表示“环绕”为负值,但即使这样也不一定正确,具体取决于编译器如何优化算术运算。

请注意,unsigned 整数溢出是明确定义的 - 您将“环绕”回 0。

【讨论】:

    【解决方案3】:

    嗯...您有一个无限循环,因为您的值 x 将始终大于 25,因为您不减小它。
    由于循环是无限的,您的值 c 达到了最大大小int(如果是 4 字节,则为 2,147,483,647)。 您可以尝试这样做以逃避无限循环:

    int main(void)
    {
        int x = 41;
        int c = 0;
    
        while (x >= 25)
        {
          c = c+1;
          x--;
        }
        printf("%i\n", c);
    }
    

    【讨论】:

      【解决方案4】:

      首先,你需要知道什么是“有符号整数溢出条件”。

      当数学运算导致超出数据类型范围的数字时出现这种情况,在您的情况下这是有符号整数溢出。

      发生这种情况是因为您的循环无限进行,因为x &gt;= 25 将始终为真。

      【讨论】:

        【解决方案5】:

        一个整数在达到最大值之前只能容纳这么多数字。在您的 while 循环中,它说在 x&gt;=25 时执行它。由于 x 是 41 并且 x 的值永远不会减少,这意味着 while 循环将始终执行,因为自 41&gt;=25 以来它始终为真。

        一个整数只能容纳数字 2,147,483,647,这意味着由于 C 将继续添加到自身,因为当 while 循环达到 2,147,483,647 时将始终为真,它会给您一个错误,因为整数不能超过它,因为它没有足够的内存。

        【讨论】:

          猜你喜欢
          • 2017-05-13
          • 1970-01-01
          • 2021-03-15
          • 1970-01-01
          • 1970-01-01
          • 2012-02-29
          • 1970-01-01
          • 1970-01-01
          • 2018-09-07
          相关资源
          最近更新 更多