首页 > 解决方案 > C 中类型转换和按位运算的结果取决于顺序

问题描述

我试图在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我所期望的。为什么会这样?

标签: cvisual-studio-codebitwise-operatorsimplicit-conversionbit-shift

解决方案


首先,您的任何代码都不是真正可靠的,并且不会按照您的期望进行。

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

所以你可以在各种小整数类型之间随意转换,最后还是会有提升int的。如果您为预期类型使用正确的格式说明符,这没有问题,但您不使用,您使用%dwhich is for int

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


话虽这么说,这条线~((~(unsigned short)0) >> 1)做了以下事情:

  • 0取type的文字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期望 aint并因此相应地打印。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 移位1u15 位,然后以某种“实现定义的方式”将结果转换为 short,这意味着在二进制补码系统上,您最终会将 0x8000 转换为 -32768。

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


推荐阅读