253

我有一个简单的程序:

#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)

谁能指出这个问题?

4

6 回答 6

363

这是相当微妙的。

程序中的每个整数文字都有一个类型。它具有哪种类型由 6.4.4.1 中的表格规定:

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类型中,它将尝试上表中指示的下一个更大的类型。因此,对于常规十进制整数文字,它类似于:

  • 尝试int
  • 如果不适合,请尝试long
  • 如果它不适合,请尝试long long

十六进制文字的行为不同!如果字面量不适合像 一样的有符号类型int,它会先尝试unsigned int,然后再尝试更大的类型。请参阅上表中的差异。

因此,在 32 位系统上,您的文字0x80000000unsigned int.

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

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

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

于 2015-12-09T15:52:30.000 回答
28

0x80000000是一个unsigned值为 2147483648 的文字。

对此应用一元减号仍会为您提供具有非零值的无符号类型。(事实上​​,对于非零值x,您最终得到的值是UINT_MAX - x + 1。)

于 2015-12-09T15:42:23.117 回答
23

这个整数文字0x80000000有类型unsigned int

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

5 整数常量的类型是可以表示其值的对应列表中的第一个。

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

所以这个表达式

-0x80000000具有相同的unsigned int类型。此外,它 0x80000000在计算以下方式的二进制补码表示中具有相同的值

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

例如,如果要编写,这会产生副作用

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

结果还会再来INT_MIN

因此在这种情况下

bal < INT32_MIN

根据通常的算术转换规则,将其与转换为 long long int 类型0无符号值进行比较。0x80000000

显然 0 小于0x80000000

于 2015-12-09T15:44:25.663 回答
12

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

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

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

原始朴素的数字常量是正数。

如果它是十进制,那么分配的类型是第一个将保存它的类型:int, long, long long

如果常量是八进制或十六进制,它会得到第一个保存它的类型:int, unsigned, long, unsigned long, long long, unsigned long long

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

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


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

#define INT32_MIN        (-2147483647 - 1)

让我们在48 位的幻想世界int里走一走。unsigned

然后0x80000000适合,int类型也是如此int-0x80000000那么是负数,打印出来的结果就不同了。

[回到现实世界]

由于0x80000000在有符号类型之前适合某些无符号类型,因为它比some_signed_MAX在 范围内要大some_unsigned_MAX,所以它是一些无符号类型。

于 2015-12-09T16:32:22.530 回答
12

数字常量0x80000000的类型为unsigned int。如果我们-0x80000000对其进行 2s 的补全数学运算,我们会得到:

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

所以-0x80000000 == 0x80000000。并且比较(0 < 0x80000000)(因为0x80000000是无符号的)是正确的。

于 2015-12-09T15:42:42.443 回答
8

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

C11:6.3.1.8/1:

[...]否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为具有有符号整数类型。

因此,bal < INT32_MIN总是true

于 2015-12-09T16:04:01.133 回答