我试图不使用头文件int, char, short, long
来打印最少<limit.h>
。因此按位操作将是一个不错的选择。但是发生了一些奇怪的事情。
声明
printf("The minimum of short: %d\n", ~(((unsigned short)~0) >> 1));
给我
The minimum of short: -32768
但声明
printf("The minimum of short: %d\n", ~((~(unsigned short)0) >> 1));
给我
The minimum of short: 0
此现象也发生在char
中。但是它不会在long, int
中发生。为什么会这样?
值得一提的是,我使用VS Code作为编辑器。当我将光标移到语句中的unsigned char
printf("The minimum of char: %d\n", (short)~((~(unsigned char)0) >> 1));
它给了我一个提示(int) 0
而不是我期望的(unsigned char)0
。为什么会这样?
答案 0 :(得分:7)
首先,您的代码中没有一个是真正可靠的,不会满足您的期望。
printf
和所有其他可变参数长度函数具有一个功能异常的“功能”,称为“ 默认参数提升”。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如char
和short
)将提升为已签名的int
。 (并且float升为两倍。)Tl; dr:printf
是一个坚果函数。
因此,您可以在所需的各种小整数类型之间进行强制转换,最后仍将提升为int
。如果您为预期的类型使用了正确的格式说明符,那么这没问题,但是您没有使用%d
来表示int
。
此外,~
运算符与C中的大多数运算符一样,对其操作数执行隐式整数提升。参见Implicit type promotion rules。
话虽这么说,这一行~((~(unsigned short)0) >> 1)
执行以下操作:
0
的文字int
并转换为unsigned short
。unsigned short
隐式提升回int
。int
值0
的按位补数。这是0xFF...FF
十六进制,-1
十进制,假定2的补数。将此int
右移1。在此,您在移负整数时调用实现定义的行为。 C允许它要么导致逻辑移位=零移位,要么导致算术移位=符号位移位。编译器与编译器之间的结果不同且不可移植。
如果发生逻辑移位,则得到0x7F...FF
,而如果发生算术移位,则得到0xFF...FF
。在这种情况下,似乎是后者,这意味着您在转换后仍然还有小数点-1
。
0xFF...FF
= -1
进行按位补码,得到0
。short
。仍然0
。int
。仍然0
。%d
期望int
并因此进行打印。 unsigned short
用%hu
打印,short
用%hd
打印。使用正确的格式说明符应消除默认参数提升的影响。建议:研究隐式类型提升,并避免在具有符号类型的操作数上使用按位运算符。
要简单地显示各种有符号类型的最低2的补码值,您必须对无符号类型进行一些欺骗,因为对它们的有符号版本进行按位运算是不可靠的。示例:
int shift = sizeof(short)*8 - 1; // 15 bits on sane systems
short s = (short) (1u << shift);
printf("%hd\n", s);
这会将无符号int 1u
移15位,然后以某种“实现定义的方式”将其结果转换为short,这意味着在二进制补码系统上,您最终将0x8000转换为-32768。
然后为printf
提供正确的格式说明符,您将从那里得到预期的结果。