C比较-1和65535/4294967295/18446744073709551615

C comparison between -1 and 65535/4294967295/18446744073709551615

我对以下说法有误解:

printf("%d\n", -1 == 65535);
printf("%d\n", -1 == 4294967295UL);
printf("%d\n", -1 == 18446744073709551615);
printf("%d\n", -1 == 18446744073709551615UL);

输出为:

0
0
0
1

如果不添加 UL 后缀,编译器会给出以下警告:main.c:36:28: warning: integer constant is so large that it is unsigned

有人可以给我解释一下吗?为什么 18446744073709551615 结果为 0 而 18446744073709551615UL 结果为 1?不加那个后缀可以吗?

谢谢

这是由于整数常量的处理方式所致。

对于没有后缀的十进制常量,编译器会检查该值以查看它是否适合以下类型,按此顺序:

  • int
  • long int
  • long long int

编译器会向您发出警告,因为值 18446744073709551615 不适合任何这些类型。接下来发生的事情由 C standard:

的第 6.4.4.1p6 节规定

If an integer constant cannot be represented by any type in its list, it may have an extended integer type, if the extended integer type can represent its value. If all of the types in the list for the constant are signed, the extended integer type shall be signed. If all of the types in the list for the constant are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. If an integer constant cannot be represented by any type in its list and has no extended integer type, then the integer constant has no type

并且由于 18446744073709551615 不适合上述类型之一,编译器将尝试将该值适合有符号的扩展类型(如果存在)。 GCC 特别支持一种名为 __int128 的类型,因此假设您使用的是 GCC,则常量具有该类型。

然后比较-1 == 18446744073709551615,把比较的左边转成类型__int128,可以做到不改变值,结果是false。

在常量 18446744073709551615UL 的情况下,后缀赋予它类型 unsigned long 并且该值确实适合该类型。然后为了比较-1 == 18446744073709551615UL-1转换为类型unsigned long。根据out-of-range有符号类型转无符号类型的规则,int-1转为unsigned long值18446744073709551615,比较为真。

gcc 生成的警告具有误导性,因为它暗示常量是无符号的,而实际上它不是。这可能是对 C89 行为的引用,其中此常量实际上具有类型 unsigned long。如果 -std=c89 标志被传递给 gcc,除了第一个警告之外,您还会收到另一个警告:warning: this decimal constant is unsigned only in ISO C90。结果也会因此而不同,第三次调用 printf 输出 1.

结果取决于编译器为整数常量选择的类型。

如果整数十进制常量没有后缀,则编译器认为以下类型表示常量:intlong intlong long int.

此值 18446744073709551615 是可以存储在类型 unsigned longunsigned long long 的对象中的最大值。它不能表示为 signed long intsigned long long int.

类型

当用后缀 ulUL 指定常量时,编译器按顺序考虑类型 unsigned long intunsigned long long int 并选择第一个类型的对象可以表示常量。

所以在这个表达式中

-1 == 18446744073709551615UL

比较的右操作数具有编译器选择的相应无符号整数类型中表示的最大值,并且由于通常的算术转换,左操作数通过提升符号位转换为该类型。结果 -1 表示为存储在所选无符号整数类型中的最大无符号值。

顺便说一下,在您使用的系统中 sizeof( unsigned long ) 似乎等于 sizeof( unsigned long long )。否则如果 sizeof( unsigned long ) 等于 sizeof( unsigned int )(就像在 Windows 中一样)那么这个调用

printf("%d\n", -1 == 4294967295UL);

也输出了1.

这似乎是导致意外结果的编译器行为不一致的示例:

  • printf("%d\n", -1 == 18446744073709551615);gcc 编译会产生警告 integer constant is so large that it is unsigned 因为它超出了类型 long long int 的范围。如果将此数字存储到 unsigned long long 中,您可能会得到预期的 unsigned long long 值,但它用于测试并且您正在将 -1 与扩展类型的正整数进行比较,因此编译器可能会在所有情况下将测试优化为 false。 gccprintf("%d\n", 18446744073709551615 == 36893488147419103231) 输出 1 因此编译器不完全使用 128 位算术来评估此测试。

  • 使用 clang 编译会产生类似的警告 integer literal is too large to be represented in a signed integer type, interpreting as unsigned 但生成的代码不同,输出为 1,这意味着18446744073709551615 在内部输入为 unsigned long long,与发出的评论一致。

这是一个程序来证实我的说法:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#define typeof(X)  _Generic((X),                                        \
                   long double: "long double",                          \
                   double: "double",                                    \
                   float: "float",                                      \
                   unsigned long long int: "unsigned long long int",    \
                   long long int: "long long int",                      \
                   unsigned long int: "unsigned long int",              \
                   long int: "long int",                                \
                   unsigned int: "unsigned int",                        \
                   int: "int",                                          \
                   unsigned short: "unsigned short",                    \
                   short: "short",                                      \
                   unsigned char: "unsigned char",                      \
                   signed char: "signed char",                          \
                   char: "char",                                        \
                   bool: "bool",                                        \
                   default: "other")

int main() {
#define TEST(x)  printf("%8s has type %s and size %zu\n", #x, typeof(x), sizeof(x))
    TEST(9223372036854775807);
    TEST(18446744073709551615U);
    TEST(18446744073709551615);
}

输出 clang:

typeof.c:27:10: warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned
      [-Wimplicitly-unsigned-literal]
    TEST(18446744073709551615);
         ^
typeof.c:27:10: warning: integer literal is too large to be represented in a signed integer type, interpreting as unsigned
      [-Wimplicitly-unsigned-literal]
2 warnings generated.

9223372036854775807 has type long int and size 8
18446744073709551615U has type unsigned long int and size 8
18446744073709551615 has type unsigned long long int and size 8

gcc输出:

typeof.c: In function `main':
typeof.c:27:30: warning: integer constant is so large that it is unsigned
     TEST(18446744073709551615);
                              ^
typeof.c:27:30: warning: integer constant is so large that it is unsigned
9223372036854775807 has type long int and size 8
18446744073709551615U has type unsigned long int and size 8
18446744073709551615 has type other and size 16

案例 3 在 GCC 中存在错误。

在 x86_64 的 GCC 11.2 中,有符号和无符号 int 类型是 32 位,有符号和无符号 longlong long 类型是 64 位,并且还有宽度为 128.

的扩展类型 __int128_t__uint128_t

-1 == 65535中,-165535都是int。它们不相等,所以比较结果为 0。

-1 == 4294967295UL中,-1是一个int4294967295UL是一个unsigned long。根据通常的算术转换,-1 转换为 unsigned long,它对模 264 进行换行,生成 18,446,744,073,709,551,615。这不等于 4,294,967,295,因此比较结果为 0。

-1 == 18446744073709551615 中,18446744073709551615 对于普通类型来说太大了。 C 2018 6.4.4.1 5 说一个无后缀的十进制整数常量的类型是intlong intlong long int中第一个可以表示它的。由于其中 none 可以在此 C 实现中表示它,因此适用第 6 段:

If an integer constant cannot be represented by any type in its list, it may have an extended integer type, if the extended integer type can represent its value. If all of the types in the list for the constant are signed, the extended integer type shall be signed…

由此可见18446744073709551615的类型应该是__int128_t。这可以通过使用 _Generic 来显示类型来确认; the following program prints “__int128_t”:

#include <stdio.h>


int main(void)
{
    printf("%s\n", _Generic(18446744073709551615,
            __int128_t: "__int128_t",
            default:    "something else")
        );
}

相比之下,Clang 13.0.0 使用 unsigned long long.](https://godbolt.org/z/a5Y3h5Wbo)

因此,GCC 的错误消息“整数常量太大以至于没有符号”是不正确的。 GCC 使用与其错误消息状态不同的类型,因此这是 GCC 中的错误。

鉴于类型是 __int128_t,-1 和 18,446,744,073,709,551,615 都可以在该类型中表示,因此表达式的求值将 -1 转换为 __int128_t,值没有变化,值是不相等,比较结果为 0.

(因为 Clang 使用 unsigned long long,它产生 1。)

为了确认,我们看到 printf("%d\n", -18446744073709551615 < 0); 打印了 “1” in GCC, showing the comparison was performed with a signed type, whereas Clang prints “0”

最后,在 -1 == 18446744073709551615UL 中,通常的算术转换将 −1 转换为 unsigned long,它对模 264 进行换行,得到 18,446,744,073,709,551,615。则两个操作数相等,比较结果为1.