为什么 0 < -0x80000000?

Why is 0 < -0x80000000?

我有下面一个简单的程序:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

条件if(bal < INT32_MIN )始终为真。怎么可能?

如果我将宏更改为:

,效果很好
#define INT32_MIN        (-2147483648L)

谁能指出问题所在?

0x80000000 是一个 unsigned 字面值,值为 2147483648。

在此 still 上应用一元减号会得到一个具有非零值的无符号类型。 (实际上,对于非零值 x,您最终得到的值是 UINT_MAX - x + 1。)

数值常量 0x80000000 的类型为 unsigned int。如果我们取 -0x80000000 并对其进行 2s 计算,我们得到:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

所以-0x80000000 == 0x80000000。比较 (0 < 0x80000000)(因为 0x80000000 是无符号的)是真的。

此整数文字 0x80000000 的类型为 unsigned int

根据 C 标准(6.4.4.1 整数常量)

5 The type of an integer constant is the first of the corresponding list in which its value can be represented.

而这个整型常量可以用unsigned int的类型来表示。

所以这个表达式

-0x80000000 具有相同的 unsigned int 类型。而且它具有相同的价值 0x80000000在补码表示中的计算方式如下

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

如果这样写的话会有副作用

int x = INT_MIN;
x = abs( x );

结果又是INT_MIN.

因此在这种情况下

bal < INT32_MIN

比较 0unsigned0x80000000 根据通常的算术转换规则转换为 long long int 类型。

显然0小于0x80000000.

这很微妙。

你程序中的每个整数文字都有一个类型。它的类型由 6.4.4.1 中的 table 规定:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

如果文字数字无法放入默认 int 类型,它将尝试上面 table 中指示的下一个更大的类型。所以对于常规的十进制整数文字,它是这样的:

  • 尝试int
  • 如果放不下,试试long
  • 如果装不下,试试long long

十六进制文字的行为有所不同! 如果文字不能放入像 int 这样的带符号类型中,它将首先尝试 unsigned int 然后再移动继续尝试更大的类型。看上面的区别table.

所以在 32 位系统上,您的文字 0x80000000unsigned int.

类型

这意味着您可以在文字上应用一元运算符 - 而无需调用实现定义的行为,否则您会在溢出有符号整数时这样做。相反,您将得到值 0x80000000,一个正值。

bal < INT32_MIN 调用通常的算术转换,表达式 0x80000000 的结果从 unsigned int 提升为 long long。值 0x80000000 被保留并且 0 小于 0x80000000,因此结果。

当您将文字替换为 2147483648L 时,您使用的是十进制表示法,因此编译器不会选择 unsigned int,而是尝试将其放入 long 中。此外,L 后缀表示您想要 long if possible。如果你继续阅读 6.4.4.1 中提到的 table,L 后缀实际上有类似的规则:如果数字不适合请求的 long,它在 32 位情况下不适合, 编译器会给你一个 long long 适合它的地方。

C 有一个规则,整数文字可能是 signedunsigned 取决于它是否适合 signedunsigned (整数提升)。在 32 位机器上,文字 0x80000000 将是 unsigned-0x80000000 的 2 的补码在 32 位机器上是 0x80000000。因此,比较 bal < INT32_MIN 是在 signedunsigned 之间,并且在根据 C 规则进行比较之前 unsigned int 将转换为 long long.

C11:6.3.1.8/1:

[...] Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.

因此,bal < INT32_MIN 总是 true

认为 - 是数字常量的一部分时会出现混淆点。

在下面的代码中,0x80000000 是数值常量。它的类型仅在此基础上确定。 - 之后应用并且 不会更改类型

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Raw 朴素的数字常量是正数。

如果它是十进制,则分配的类型是将保存它的第一个类型:intlonglong long.

如果常量是八进制或十六进制,它会获取保存它的第一个类型:intunsignedlongunsigned longlong long, unsigned long long.

0x80000000,在 OP 的系统上获取 unsignedunsigned long 的类型。无论哪种方式,它都是某种无符号类型。

-0x80000000 也是一些非零值并且是某种无符号类型,它大于 0。当代码将其与 long long 进行比较时,values 在比较的两侧没有改变,所以 0 < INT32_MIN 为真。


另一种定义避免了这种奇怪的行为

#define INT32_MIN        (-2147483647 - 1)

让我们在 intunsigned 是 48 位的幻想世界里走一会儿。

那么 0x80000000 适合 int 类型 int 也适合。 -0x80000000则为负数,打印出来的结果不一样。

[返回真实世界]

由于 0x80000000 在有符号类型之前适合一些无符号类型,因为它刚好大于 some_signed_MAX 但在 some_unsigned_MAX 之内,它是一些无符号类型。