该声明
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,这是我所期望的.为什么会这样?
解决方法
printf和所有其他变量参数长度函数都有一个功能失调的“功能”,称为默认参数提升.这意味着传递的参数的实际类型经过静默促销.小整数类型(例如char和short)被提升为已签名的int. (并且浮动被提升为两倍.)Tl; dr:printf是一个坚果功能.
因此你可以在所有你想要的各种小整数类型之间进行转换,到最后仍然会有一个提升到int.如果您对预期类型使用正确的格式说明符,则没有问题,但是您没有使用%d,这是针对int的.
此外,〜运算符与C中的大多数运算符一样,对其操作数执行隐式整数提升.见Implicit type promotion rules.
话虽如此,这一行〜((〜(无符号短)0)>> 1)执行以下操作:
>取字体0的类型为int并转换为unsigned short.
>通过隐式整数提升隐式地将无符号short返回int.
>计算int值0的按位补码.这是0xFF … FF hex,-1 dec,假设2的补码.
>右移此int为1.这里,您在移动负整数时调用实现定义的行为. C允许这导致逻辑移位=零移位,或算术移位=符号位移位.从编译器到编译器和非可移植的结果不同.
在逻辑移位的情况下,您可以获得0x7F … FF或在算术移位时获得0xFF … FF.在这种情况下,它似乎是后者,这意味着你在转换后仍然有十进制-1.
>你做0xFF的按位补码… FF = -1并得到0.
>你把它简化了.仍为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正确的格式说明符,你将从那里得到预期的结果.