C中类型转换和按位运算的结果取决于顺序
Result of type cast and bitwise operation in C depends on the order
我试图在不使用头文件 <limit.h>
的情况下打印 int, char, short, long
的最小值。所以按位运算会是一个不错的选择。但是奇怪的事情发生了。
声明
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
。为什么会这样?
首先,none 您的代码非常可靠,不会达到您的预期。
printf
和所有其他可变参数长度函数都有一个功能失调的“功能”,称为 默认参数提升 。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如 char
和 short
)被提升为带符号的 int
。 (并且 float 被提升为 double。)Tl;dr:printf
是一个疯狂的函数。
所以各种小整数类型之间可以任意转换,最后还是会升到int
。如果您为预期类型使用正确的格式说明符,这没有问题,但您没有使用 %d
,它用于 int
.
此外,~
运算符与 C 中的大多数运算符一样,对其操作数执行隐式整数提升。参见 。
也就是说,这一行 ~((~(unsigned short)0) >> 1)
执行以下操作:
取 int
类型的文字 0
并转换为 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);
这会将一个 unsigned int 1u
移位 15 位,然后将其结果转换为 short,以某种“实现定义的方式”,这意味着在二进制补码系统上,您最终会将 0x8000 转换为 - 32768.
然后给 printf
正确的格式说明符,您将从那里得到预期的结果。
我试图在不使用头文件 <limit.h>
的情况下打印 int, char, short, long
的最小值。所以按位运算会是一个不错的选择。但是奇怪的事情发生了。
声明
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
。为什么会这样?
首先,none 您的代码非常可靠,不会达到您的预期。
printf
和所有其他可变参数长度函数都有一个功能失调的“功能”,称为 默认参数提升 。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如 char
和 short
)被提升为带符号的 int
。 (并且 float 被提升为 double。)Tl;dr:printf
是一个疯狂的函数。
所以各种小整数类型之间可以任意转换,最后还是会升到int
。如果您为预期类型使用正确的格式说明符,这没有问题,但您没有使用 %d
,它用于 int
.
此外,~
运算符与 C 中的大多数运算符一样,对其操作数执行隐式整数提升。参见
也就是说,这一行 ~((~(unsigned short)0) >> 1)
执行以下操作:
取
int
类型的文字0
并转换为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);
这会将一个 unsigned int 1u
移位 15 位,然后将其结果转换为 short,以某种“实现定义的方式”,这意味着在二进制补码系统上,您最终会将 0x8000 转换为 - 32768.
然后给 printf
正确的格式说明符,您将从那里得到预期的结果。