【问题标题】:Why does the negation of the minimum possible integer yield itself?为什么最小可能整数的否定会产生自身?
【发布时间】:2017-12-18 18:26:12
【问题描述】:

所以我写了一个小实验测试下溢和上溢,使用 c 和 64 位机器。对于 int 类型,最小/最大值是:

   int tmax = 2147483647;
   int tmin = -2147483648;

我知道二进制补码是如何工作的,这不是我的问题。

所以我想,如果我做了一些负 tmin 会发生什么?那就是:

   int tmin = -2147483648;
   int negativeTmin = -tmin;

它最终仍然是 tmin。 (也就是说,negativeTmin 将是 -2147483648)

我的问题是为什么会这样? 因为正数 2,147,483,648 不能用 int 表示,所以我理解为什么当然不是这样,但它根本没有改变似乎很奇怪使它成为唯一一个在应用 - 时不会改变的非零 int。我并不是说我对它应该是什么有更好的了解,我只是好奇为什么 -tmin == tmin。它是否与按位运算有关,或者在计算机中如何进行减法,或者它是否默认这样做是因为我正在尝试做的是未定义的,或者其他什么?

我的代码:

#include <stdio.h>
int main() {
   int tmax = 2147483647;
   printf("tmax Before: %d\n", tmax);
   tmax++;
   printf("tmax After: %d\n\n", tmax);

   int tmin = -2147483648;
   printf("tmin Before: %d\n", tmin);
   tmin--;
   printf("tmin After: %d\n\n", tmin);

   int tmin2 = -2147483648;
   int negativeTmin = -tmin2;
   printf("negative tmin: %d\n\n", negativeTmin);

   return 0;
}

输出:

tmax 之前:2147483647 tmax 之后:-2147483648

tmin 之前:-2147483648 tmin 之后:2147483647

负 tmin:-2147483648

【问题讨论】:

  • 您的“实验”涉及未定义的行为(有符号溢出有一个)。所以从 C 的角度来看,推理它有一点价值。
  • 您说您了解补码 2 表示。你确定吗?这是 tmin==-tmin 的解释。为方便起见,请在 8 位中仔细考虑。
  • 你为什么不使用来自limits.hINT_MAXINT_MIN
  • Eugene Sh:我明白了。 Yunnosch:是的,在发布这个问题后不久,我就想到了你在说什么(下)。然而,这不是正确的解决方案,相反我被告知它是未定义的,因为不能保证所有机器的 tmin == -tmin。 David Bowling:因为我想你可以这么说,因为我想弄乱他们“原始”形式的限制。
  • 您系统上的 INT_MAX 和 INT_MIN 是什么?非常相关的信息。另请确认您使用的是 C99 或更高版本(如果您有 32 位整数,-2147483648 的行为在 C90 和 C99 之间发生了变化)

标签: c integer storage min twos-complement


【解决方案1】:

正如其他人在此处发布的那样,从技术上讲,您正在做的事情会导致未定义的行为,因为 C 中带符号整数的溢出或下溢会导致未定义的行为。

另一方面,在大多数英特尔系统上,整数溢出或下溢只是将整数值环绕并设置一些处理器标志,以便将来的指令可以检测到溢出。在这些系统上,有理由问 - 为什么在计算 -Tmin会得到 Tmin

在带符号的二进制补码系统中,请注意表达式-x 等价于~x + 1。所以让我们假设你有 Tmin,它看起来像这样:

10000000 00000000 00000000 00000000

如果你计算 ~Tmin,你会得到

01111111 11111111 11111111 11111111

这恰好是 Tmax。如果你加一个,你会得到一个巨大的涟漪,一直传播到最后,产生

10000000 00000000 00000000 00000000

这是我们开始的。所以这就是为什么你可能会看到 Tmin 回来了。

另一种理解方式:您知道有符号 32 位整数的 Tmin 是 -231。 -Tmin 的值应该是 Tmin + -Tmin = 0 (mod 232支持>)。那么 [-231, 231 - 1] 范围内的哪个值恰好具有此属性?它是 -231,这就是为什么 Tmin = -Tmin

因此,您的问题的最佳答案可能是“从技术上讲,您所做的是未定义的行为,但在合理的英特尔系统和未设置为进行积极优化的编译器上,它归结为如何带符号的 32 位整数运算的工作原理以及如何定义求反。”

【讨论】:

    【解决方案2】:

    您的代码 int tmin2 = -2147483648; int negativeTmin = -tmin2 由于整数溢出而引入了未定义的行为,因此它可能会产生 any 结果。因此,考虑任何规则为什么会发生这种情况以及它是否与二进制补码有关是没有意义的,实际上是错误的。

    积分溢出是未定义行为的示例,正如标准的“未定义行为”定义中提到的示例 (3.4.3 - undefined behavior):

    1 未定义的行为行为,在使用不可移植或错误的 程序构造或错误数据,为此国际 标准没有要求

    2 注意可能的未定义行为范围从忽略情况 完全具有不可预测的结果,在翻译过程中表现得很好 或以文件化方式执行程序的特征 环境(无论是否发布诊断消息),以 终止翻译或执行(发出 诊断信息)。

    3 示例 未定义行为的一个示例是整数行为 溢出。

    【讨论】:

    • 虽然代码表现出未定义的行为并且可能产生任意结果当然是事实,但了解二进制补码算法及其溢出行为在调试中经常有用,即回溯意外值如何可能已经生成,并且当然会出现在具有明确定义的二进制补码语义的其他语言中。因此,我会犹豫将这个问题打折扣为 UB,而无需进一步解释,尽管 C 的细节可能会被省略。
    • 虽然不同的行为是可能的,例如优化器使用更广泛的本机算术甚至捕获实现,但二进制补码溢出仍然是最常见的最终结果。坦率地说,我发现自己根据有限的信息更频繁地推断出崩溃系统的先前状态,这超出了我的预期。虽然我最终可能会检查程序集以验证假设,但包装的整数通常是一个有价值的线索。
    • @harold 试试INT_MIN / -1
    • 在算术上,除以负数是一样的
    • @MM“为什么 X 会发生”没有完全回答为“它不能保证会发生”又名“OMG UB”,这很有趣,而不是解释 为什么.
    【解决方案3】:

    -X 是 X 的 2s 补码
    这就是硬件对否定的作用https://c9x.me/x86/html/file_module_x86_id_216.html

    INT_MIN = -2147483648 = 0x80000000
    -2147483648 的 2 的补码 = 0x80000000

    您可以将 2 的补码计算为翻转位并加 1
    请参阅https://en.wikipedia.org/wiki/Two%27s_complement
    翻转 0x80000000 的位会得到 0x7fffffff。
    0x7fffffff + 1 = 0x80000000 = -2147483648

    (gdb) p /x (int)(-2147483648)
    $14 = 0x80000000
    (gdb) p /x (int)-(-2147483648)
    $15 = 0x80000000
    (gdb) p /x ~(0x80000000)
    $16 = 0x7fffffff
    (gdb) p /x ~(0x80000000) + 1
    $17 = 0x80000000

    【讨论】:

      【解决方案4】:

      另一种思考方式。

      int 类型的数据用 32 位表示。取 tmin = -2147483648,那么当然是 -tmin = 2147483648。在二进制补码算法中,tmin 的二进制表示为

      10000000 00000000 00000000 00000000

      并且,对于 -tmin

      0 10000000 00000000 00000000 00000000

      但只允许 32 位,因此截断消除了最高有效位(在本例中为第一个零),我们得到

      10000000 00000000 00000000 00000000

      这是tmin

      【讨论】:

        【解决方案5】:

        其实我相信我刚刚意识到了答案;正如我们所知,2147483648 不能用 int 表示,-2147483648 也是如此,所以当你制作 -(-2147483648) 时,它就是 2147483648,但不能像上面描述的那样表示,变成 -2147483648。因此,为什么 -tmin == tmin。

        【讨论】:

        • 你说得对,这里的问题是值超出了范围,但仅凭这一事实并不能解释为什么你会得到你开始的东西。这与带符号的二进制补码算术的工作方式有关,我试图在我的回答中解释一下。
        • 如你所写,对于 32 位 int2147483648 不是 int 常量,它是 longlong long 常量,而 -2147483648 是相同类型的常量表达式,但由于其值适合int类型,int tmin = -2147483648;不会导致溢出,因此longlong long常量表达式只是转换为int
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-16
        • 2023-03-11
        • 1970-01-01
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多