-1 和 65535/4294967295/18446744073709551615 之间的 C 比较



我对以下陈述有误解:

printf("%dn", -1 == 65535);
printf("%dn", -1 == 4294967295UL);
printf("%dn", -1 == 18446744073709551615);
printf("%dn", -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?不添加后缀可以吗?

谢谢

案例 3 在 GCC 中表现出一个错误。

在 GCC 11.2 for x86_64 中,有符号和无符号int类型为 32 位,有符号和无符号longlong long类型为 64 位,还有宽度为 128 的扩展类型__int128_t__uint128_t

-1 == 65535-165535都是int。它们是不相等的,因此比较产生 0。

-1 == 4294967295UL中,-1int4294967295ULunsigned 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中的第一个。由于这些都不能在此 C 实现中表示它,因此第 6 段适用:

如果整数

常量不能由其列表中的任何类型表示,则它可能具有扩展整数类型(如果扩展整数类型可以表示其值)。如果常量列表中的所有类型都有符号,则扩展整数类型应有符号...

由此,我们看到18446744073709551615的类型应该__int128_t。通过使用_Generic来显示类型来确认这一点;以下程序打印"__int128_t":

#include <stdio.h>

int main(void)
{
printf("%sn", _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("%dn", -18446744073709551615 < 0);在GCC中打印"1",显示比较是用有符号类型执行的,而Clang打印"0"。

最后,在-1 == 18446744073709551615UL,通常的算术转换将?1转换为unsigned long,包裹模264,产生18,446,744,073,709,551,615。然后两个操作数相等,比较产生 1。

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

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

  • int
  • long int
  • long long int

编译器会发出警告,因为值18446744073709551615不适合任何这些类型。 接下来会发生什么由 C 标准的第 6.4.4.1p6 节规定:

如果整数常量不能由其列表中的任何类型表示, 它可能具有扩展整数类型,如果扩展整数类型可以 表示其值。如果常量列表中的所有类型 有符号的,扩展整数类型应有符号。如果所有 常量列表中的类型是无符号的,扩展整数 类型应无符号。如果列表同时包含已签名和未签名 类型,扩展整数类型可以是有符号的,也可以是无符号的。如果 整数常量不能由其列表中的任何类型表示,并且具有 没有扩展整数类型,则整数常量没有类型

由于18446744073709551615不适合上述类型之一,因此编译器将尝试将该值放入有符号扩展类型(如果存在)。 GCC特别支持一种叫做__int128的类型,所以假设你使用的是GCC,那么常量就是这个类型。

然后对于比较-1 == 18446744073709551615,比较的左侧被转换为类型__int128它可以在不改变值的情况下完成,结果为 false。

在常量18446744073709551615UL的情况下,后缀给它类型unsigned long,并且该值确实适合该类型。 然后对于比较-1 == 18446744073709551615UL-1转换为类型unsigned long。 根据将超出范围的有符号类型转换为无符号类型的规则,int-1将转换为unsigned long值18446744073709551615,并且比较为 true。

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("%dn", -1 == 4294967295UL);

还输出了1.

这似乎是编译器行为不一致的一个例子,导致令人惊讶的结果:

  • 使用gcc编译printf("%dn", -1 == 18446744073709551615);会产生警告integer constant is so large that it is unsigned,因为它超出了类型long long int的范围。如果要将此数字存储到unsigned long long中,您可能会得到预期的无符号长整型值,但它用于测试中,并且您正在将-1与扩展类型的正整数进行比较,因此编译器可能只是在所有情况下都将测试优化为 false。GCC输出1用于printf("%dn", 18446744073709551615 == 36893488147419103231),因此编译器不会完全使用 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 %zun", #x, typeof(x), sizeof(x))
TEST(9223372036854775807);
TEST(18446744073709551615U);
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]
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

相关内容

  • 没有找到相关文章

最新更新