【问题标题】:When R.H.S have negative int and unsigned int outside the range of int in arithmetic operation当 R.H.S 在算术运算中有负 int 和 unsigned int 超出 int 范围时
【发布时间】:2018-11-12 09:56:12
【问题描述】:

我为这个标题道歉,因为我不得不以某种方式找到一个独特的标题。

考虑下面的代码:

#include<stdio.h>
int main(void)
{
  int b = 2147483648; // To show the maximum value of int type here is 2147483647
  printf("%d\n",b);

  unsigned int a = 2147483650;
  unsigned int c = a+(-1);
  printf("%u\n",c);
}

上述程序在 64 位操作系统上使用 gcc 编译器运行时的输出是:

-2147483648
 2147483649

请看我对案例的理解:

Unsigned int a 超出了signed int 类型的范围。在 R.H.S (-1) 将转换为 unsigned int 因为操作数是不同的类型。将 -1 转换为 unsigned int 的结果是:

-1 + (unsigned int MAX_UINT +1) = unsigned int MAX_UINT = 4294967295.

现在 R.H.S 将是:

unsigned int MAX_UINT + 2147483650

现在看起来它超出了 unsigned int 的范围。我不知道如何从这里开始,看起来即使我继续进行这个解释,我也不会达到经验输出。

请给出正确的解释。

PS:要知道int b = 2147483648 是如何变成-2147483648 的不是我的本意。我刚刚在代码中添加了那行,所以很明显2147483650 不在int 的范围内。

【问题讨论】:

  • 2147483648 不是int 类型的最大值——它不能用int 表示。
  • @WeatherVane:我知道不是。我用这个值表明 2147483650 超出了 int 的范围。
  • 您要求解释未定义的行为。 2147483648 不能用 int 表示,这就是为什么 INT_MIN 被定义为 (-2147483647 - 1) 而不是 -2147483648
  • @WeatherVane:我不是在问 2147483648 的行为。我想知道我们如何得到变量 c 的结果为 2147483649。

标签: c unsigned-integer integer-arithmetic


【解决方案1】:

2147483648 不是 32 位的 int,它刚好在 INT_MAX 之上,在此类平台上其值为 2147483647

int b = 2147483648; 是实现定义的。在您的平台上,它似乎执行 32 位回绕,这是典型的二进制补码架构,但 C 标准不保证。

因此printf("%d\n", b); 输出-2147483648

其余代码在 32 位系统上完美定义,输出 2147483649 正确且符合预期。 64 位 的操作系统在评估步骤中起着非常微妙的作用,但与实际结果几乎无关,这完全由 C 标准定义。

以下是步骤:

  • unsigned int a = 2147483650; 毫不奇怪,aunsigned int,它的初始化器是 intlong intlong long int,具体取决于这些类型中的哪一个具有至少 32 个值位。在 Windows 和 32 位 linux 上,它将是 long long int,而在 64 位 linux 上,它将是 long int。该值在存储到unsigned int 变量时被截断为 32 位。

    您可以通过添加以下代码来验证这些步骤:

    printf("sizeof(2147483650) -> %d\n", (int)sizeof(2147483650));
    printf("         sizeof(a) -> %d\n", (int)sizeof(a));
    
  • 第二个定义unsigned int c = a+(-1);经历相同的步骤:

    • c 被定义为 unsigned int,并且它的初始化程序在存储到 c 时被截断为 32 位。初始化器是一个加法:
    • 第一项是unsigned int,值为2147483650U
    • 第二项是一个带括号的表达式,它对值为1int 进行一元否定。因此,它是一个 int,其值为 -1,正如您正确分析的那样。
    • 第二项转换为unsigned int:转换为模232,因此值为4294967295U
    • 然后使用无符号算术执行加法,它被指定为以unsigned int 类型的宽度为模,因此结果是unsigned int 的值为2147483649U,(6442450945 模232)
    • unsigned int 值存储在c 中,并使用printf("%u\n", c); 正确打印为2147483649

如果表达式改为 2147483650 + (-1),则计算将在 64 位有符号算术中进行,类型为 long intlong long int,具体取决于架构,结果为 2147483649。当存储到 c 时,该值将被截断为 32 位,因此 c 的值与 2147483649 相同。

请注意,上述步骤不依赖于负值的实际表示。它们是为所有架构完全定义的,只有int 类型的宽度很重要。

您可以使用额外的代码来验证这些步骤。下面是一个完整的检测程序来说明这些步骤:

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

int main(void) {
    printf("\n");
    printf("             sizeof(int) -> %d\n", (int)sizeof(int));
    printf("    sizeof(unsigned int) -> %d\n", (int)sizeof(unsigned int));
    printf("        sizeof(long int) -> %d\n", (int)sizeof(long int));
    printf("   sizeof(long long int) -> %d\n", (int)sizeof(long long int));
    printf("\n");

    int b = 2147483647; // To show the maximum value of int type here is 2147483647
    printf("                   int b =  2147483647;\n");
    printf("                       b -> %d\n",b);
    printf("               sizeof(b) -> %d\n", (int)sizeof(b));
    printf("      sizeof(2147483647) -> %d\n", (int)sizeof(2147483647));
    printf("      sizeof(2147483648) -> %d\n", (int)sizeof(2147483648));
    printf("     sizeof(2147483648U) -> %d\n", (int)sizeof(2147483648U));
    printf("\n");

    unsigned int a = 2147483650;
    printf("          unsigned int a =  2147483650;\n");
    printf("                       a -> %u\n", a);
    printf("     sizeof(2147483650U) -> %d\n", (int)sizeof(2147483650U));
    printf("      sizeof(2147483650) -> %d\n", (int)sizeof(2147483650));
    printf("\n");

    unsigned int c = a+(-1);
    printf("          unsigned int c =  a+(-1);\n");
    printf("                       c -> %u\n", c);
    printf("               sizeof(c) -> %d\n", (int)sizeof(c));
    printf("                  a+(-1) -> %u\n", a+(-1));
    printf("          sizeof(a+(-1)) -> %d\n", (int)sizeof(a+(-1)));
#if LONG_MAX == 2147483647
    printf("         2147483650+(-1) -> %lld\n", 2147483650+(-1));
#else
    printf("         2147483650+(-1) -> %ld\n", 2147483650+(-1));
#endif
    printf(" sizeof(2147483650+(-1)) -> %d\n", (int)sizeof(2147483650+(-1)));
    printf("        2147483650U+(-1) -> %u\n", 2147483650U+(-1));
    printf("sizeof(2147483650U+(-1)) -> %d\n", (int)sizeof(2147483650U+(-1)));
    printf("\n");

    return 0;
}

输出:

             sizeof(int) -> 4
    sizeof(unsigned int) -> 4
        sizeof(long int) -> 8
   sizeof(long long int) -> 8

                   int b =  2147483647;
                       b -> 2147483647
               sizeof(b) -> 4
      sizeof(2147483647) -> 4
      sizeof(2147483648) -> 8
     sizeof(2147483648U) -> 4

          unsigned int a =  2147483650;
                       a -> 2147483650
     sizeof(2147483650U) -> 4
      sizeof(2147483650) -> 8

          unsigned int c =  a+(-1);
                       c -> 2147483649
               sizeof(c) -> 4
                  a+(-1) -> 2147483649
          sizeof(a+(-1)) -> 4
         2147483650+(-1) -> 2147483649
 sizeof(2147483650+(-1)) -> 8
        2147483650U+(-1) -> 2147483649
sizeof(2147483650U+(-1)) -> 4

【讨论】:

  • 第二部分如何直截了当?请解释如何将 -1 转换为 unsigned int。
  • 我到了,正在编写扩展答案
  • 谢谢。这有帮助。所以简而言之,当无符号算术溢出时,模块加法完成,因此结果。
  • @Fawaz.A.R:无符号算术实际上永远不会溢出,因为它被定义为模算术。所有操作均以 2^32 为模执行。
  • 有符号算术怎么样?
【解决方案2】:

这样看

 2147483650  0x80000002
+        -1 +0xFFFFFFFF
 ----------  ----------
 2147483649  0x80000001

0xFFFFFFFF 来自哪里?好吧,00x00000000,如果你从中减去1,你会得到0xFFFFFFFF,因为无符号算术被明确定义为“换行”。

或者进一步使用十进制版本,0 - 1UINT_MAX,因为 unsigned int 换行,总和也是如此。

your value      2147483650
UINT_MAX      + 4294967295
                ----------
                6442450945
modulo 2^32   % 4294967296
                ----------
                2147483649

【讨论】:

    【解决方案3】:
    int b = 2147483648;
    printf("%d\n",b);
    // -2147483648
    
    超出目标有符号类型范围的整数(任何有符号或无符号)的

    转换

    ...结果是实现定义的,或者引发了实现定义的信号。 C11 §6.3.1.3 3

    在您使用带符号整数 2147483648 的情况下,实现定义的行为似乎将源 2147483648 的最低 32 位映射到您的 int 的 32 位。这可能不是其他编译器的结果。


    a+(-1) 等同于a + (-(1u)) 等同于a + (-1u + UINT_MAX + 1u) 等同于a + UINT_MAXaddition 溢出了unsigned 范围,但 unsigned 溢出环绕。所以总和是2147483649之前分配。使用下面的代码,没有超出范围的转换。唯一的转换是signed 1unsigned 1long 2147483650(或long long 2147483650)到unsigned 2147483650。两者都在范围转换中。

    unsigned int a = 2147483650;
    unsigned int c = a+(-1);
    printf("%u\n",c);
    //  2147483649
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      • 2011-05-19
      • 1970-01-01
      • 2018-10-20
      • 2014-10-15
      • 2012-12-17
      • 1970-01-01
      相关资源
      最近更新 更多