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

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

有人能指出这个问题吗?


当前回答

这是相当微妙的。

程序中的每个整型字面值都有一个类型。它的类型由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 如果不合身,试试长一点 如果装不下,就试试长长。

但十六进制文字的表现不同!如果字面量不适合int这样的有符号类型,它将首先尝试unsigned int,然后再尝试更大的类型。请参见上表中的差异。

所以在32位系统中,你的文字0x80000000是unsigned int类型。

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

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

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

其他回答

0x80000000是一个无符号文字,值为2147483648。

对它应用一元减号仍然会得到一个非零值的无符号类型。(事实上,对于一个非零值x,你最终得到的值是UINT_MAX - x + 1。)

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

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

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

在认为-是数字常数的一部分时,出现了一个混淆点。

在下面的代码中,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的系统上获取unsigned或unsigned long类型。无论哪种方式,它都是某种无符号类型。

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


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

#define INT32_MIN        (-2147483647 - 1)

让我们在幻想的土地上漫步一会儿,在那里int和unsigned是48位的。

那么0x80000000适合int类型,int类型也是。-0x80000000是一个负数,输出的结果是不同的。

[回到现实]

由于0x80000000适合于有符号类型之前的某个无符号类型,因为它只是在some_unsigned_MAX中大于some_unsigned_MAX,因此它是某个无符号类型。

这是相当微妙的。

程序中的每个整型字面值都有一个类型。它的类型由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 如果不合身,试试长一点 如果装不下,就试试长长。

但十六进制文字的表现不同!如果字面量不适合int这样的有符号类型,它将首先尝试unsigned int,然后再尝试更大的类型。请参见上表中的差异。

所以在32位系统中,你的文字0x80000000是unsigned int类型。

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

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

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

这个整数字面值0x80000000的类型是unsigned int。

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

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

这个整数常量可以用unsigned int类型表示。

这个表达式

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

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

这有一个副作用,如果写

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

结果仍然是INT_MIN。

因此在这种情况下

bal < INT32_MIN

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

显然,0小于0x80000000。