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

Riv*_*all 6 c bit-shift bitwise-operators implicit-conversion visual-studio-code

我试图在int, char, short, long不使用头文件的情况下打印最小值<limit.h>.所以按位操作将是一个不错的选择.但有些奇怪的事发生了

该声明

printf("The minimum of short: %d\n", ~(((unsigned short)~0) >> 1));
Run Code Online (Sandbox Code Playgroud)

给我

The minimum of short: -32768
Run Code Online (Sandbox Code Playgroud)

但声明

printf("The minimum of short: %d\n", ~((~(unsigned short)0) >> 1));
Run Code Online (Sandbox Code Playgroud)

给我

The minimum of short: 0
Run Code Online (Sandbox Code Playgroud)

这种现象也发生在char.但它不会发生在long, int.为什么会这样?

值得一提的是我使用VS Code作为我的编辑器.当我unsigned char在语句中移动光标时

printf("The minimum of char: %d\n", (short)~((~(unsigned char)0) >> 1));
Run Code Online (Sandbox Code Playgroud)

它给了我一个提示,(int) 0而不是(unsigned char)0我所期望的.为什么会这样?

Lun*_*din 7

首先,您的代码都不是真正可靠的,也不会达到预期效果.

printf并且所有其他变量参数长度函数都有一个功能失调的"功能",称为默认参数提升.这意味着传递的参数的实际类型经过静默促销.小整数类型(例如charshort)被提升为int已签名的.(并且浮动被提升为双倍.)Tl;博士:printf是一个坚果功能.

因此你可以在你想要的各种小整数类型之间进行转换int,到最后仍然会有一个提升.如果您为预期的类型使用正确的格式说明符,则没有问题,但是您没有使用%dint.

此外,与~C中的大多数运算符一样,运算符执行其操作数的隐式整数提升.请参阅隐式类型提升规则.


话虽这么说,这一行~((~(unsigned short)0) >> 1)做了以下几点:

  • 0类型的文字int并转换为unsigned short.
  • 通过隐式整数提升隐式推广unsigned short回来int.
  • 计算int值的按位补码0.这是0xFF...FF十六进制,-1十进制,假设是2的补码.
  • 右移此方法int1.在此处,您可以在移动负整数时调用实现定义的行为.C允许这导致逻辑移位=零移位,或算术移位=符号位移位.从编译器到编译器和非可移植的结果不同.

    无论0x7F...FF是逻辑移位还是0xFF...FF算术移位,都可以获得.在这种情况下,它似乎是后者,这意味着你-1在转换后仍然有小数.

  • 你做0xFF...FF=的按位补充-1和得到0.
  • 你把它投了short.还是0.
  • 默认参数提升将其转换为int.还是0.
  • %d期望a int并因此打印.unsigned short印有%hushort%hd.使用正确的格式说明符应该撤消默认参数提升的效果.

建议:研究隐式类型提升并避免在具有签名类型的操作数上使用按位运算符.

要简单地显示各种签名类型的最低2的补码值,您必须使用无符号类型做一些技巧,因为对其签名版本的按位运算是不可靠的.例:

int shift = sizeof(short)*8 - 1;  // 15 bits on sane systems
short s = (short) (1u << shift);
printf("%hd\n", s);
Run Code Online (Sandbox Code Playgroud)

这会将无符号整数1u转换为15位,然后将其结果转换为short,以某种"实现定义的方式",这意味着在两个补码系统上,您最终将0x8000转换为-32768.

然后给出printf正确的格式说明符,你将从那里得到预期的结果.