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 和所有其他可变参数长度函数都有一个功能失调的“功能”,称为 默认参数提升 。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如 charshort)被提升为带符号的 int。 (并且 float 被提升为 double。)Tl;dr:printf 是一个疯狂的函数。

所以各种小整数类型之间可以任意转换,最后还是会升到int。如果您为预期类型使用正确的格式说明符,这没有问题,但您没有使用 %d ,它用于 int.

此外,~ 运算符与 C 中的大多数运算符一样,对其操作数执行隐式整数提升。参见


也就是说,这一行 ~((~(unsigned short)0) >> 1) 执行以下操作:

  • int 类型的文字 0 并转换为 unsigned short.

  • 通过隐式整数提升将 unsigned short 隐式提升回 int

  • 计算 int0 的按位补码。这是 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 正确的格式说明符,您将从那里得到预期的结果。