C中类型转换和按位运算的结果取决于顺序

时间:2018-06-28 06:17:28

标签: c visual-studio-code bitwise-operators implicit-conversion bit-shift

我试图不使用头文件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。为什么会这样?

1 个答案:

答案 0 :(得分:7)

首先,您的代码中没有一个是真正可靠的,不会满足您的期望。

printf和所有其他可变参数长度函数具有一个功能异常的“功能”,称为“ 默认参数提升”。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如charshort)将提升为已签名的int。 (并且float升为两倍。)Tl; dr:printf是一个坚果函数。

因此,您可以在所需的各种小整数类型之间进行强制转换,最后仍将提升为int。如果您为预期的类型使用了正确的格式说明符,那么这没问题,但是您没有使用%d来表示int

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


话虽这么说,这一行~((~(unsigned short)0) >> 1)执行以下操作:

  • 采用类型为0的文字int并转换为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);

这会将无符号int 1u移15位,然后以某种“实现定义的方式”将其结果转换为short,这意味着在二进制补码系统上,您最终将0x8000转换为-32768。

然后为printf提供正确的格式说明符,您将从那里得到预期的结果。