odz*_*hko 39 c operators bit-shift integer-promotion language-lawyer
我有以下代码:
unsigned char x = 255;
printf("%x\n", x); // ff
unsigned char tmp = x << 7;
unsigned char y = tmp >> 7;
printf("%x\n", y); // 1
unsigned char z = (x << 7) >> 7;
printf("%x\n", z); // ff
Run Code Online (Sandbox Code Playgroud)
我会期望y
并且z
是一样的。但它们因是否使用中间变量而异。知道为什么会这样会很有趣。
chq*_*lie 27
这个小测试实际上比它看起来更微妙,因为行为是实现定义的:
unsigned char x = 255;
这里没有歧义,x
是一个unsigned char
带值255
,类型unsigned char
保证有足够的范围来存储255
。
printf("%x\n", x);
这产生ff
在标准输出但它是清洁器写printf("%hhx\n", x);
为printf
期望的unsigned int
转换%x
,这x
是没有的。传递x
实际上可能传递一个int
或一个unsigned int
参数。
unsigned char tmp = x << 7;
为了评价表达x << 7
,x
作为一个unsigned char
第一经历整数优惠在C标准中定义6.3.3.1:如果一个int
可以表示原始类型的所有值(如由宽度的限制,对于一个位字段),该值被转换为的int
; 否则,它被转换为unsigned int
。这些被称为整数提升。
因此,如果 in 中的值位数unsigned char
小于或等于int
(目前最常见的情况是 8 对 31),x
则首先将其提升为int
具有相同值的 ,然后向左移动7
位置。结果 ,0x7f80
保证适合int
类型,因此行为定义良好,将此值转换为类型unsigned char
将有效地截断值的高位。如果 typeunsigned char
有 8 位,则值将是128
( 0x80
),但如果 typeunsigned char
有更多位,则中的值tmp
可以是0x180
、0x380
、0x780
、0xf80
、0x1f80
,0x3f80
甚至是0x7f80
。
如果类型unsigned char
大于int
,这可能发生在罕见的系统上sizeof(int) == 1
,其中,x
被提升到unsigned int
并在此类型上执行左移。值 is 0x7f80U
,它保证适合类型unsigned int
并且存储它tmp
实际上不会丢失任何信息,因为类型unsigned char
与unsigned int
. 所以在这种情况下tmp
会有价值0x7f80
。
unsigned char y = tmp >> 7;
求值过程同上,tmp
被提升到int
或unsigned int
依赖于系统,该系统保留其值,并将该值右移 7 个位置,这是完全定义的,因为7
小于类型 ( int
or unsigned int
)的宽度,并且值为正。根据类型的位数,unsigned char
存储在中的值y
可以是1
, 3
, 7
, 15
, 31
, 63
, 127
or 255
,最常见的体系结构将具有y == 1
。
printf("%x\n", y);
再次,这将是更好吨写入printf("%hhx\n", y);
和输出可以是1
(最常见的情况),或者3
,7
,f
,1f
,3f
,7f
或ff
根据在类型值的位的数目unsigned char
。
unsigned char z = (x << 7) >> 7;
x
如上所述执行整数提升,255
然后将值 ( ) 左移 7 位作为 anint
或 an unsigned int
,始终产生0x7f80
然后右移 7 个位置,最终值为0xff
。这种行为是完全定义的。
printf("%x\n", z);
再一次,格式字符串应该是printf("%hhx\n", z);
,输出将始终是ff
.
如今,字节超过 8 位的系统变得越来越少,但某些嵌入式处理器(例如专用 DSP)仍在这样做。当unsigned char
为%x
转换说明符传递 an 时,需要一个反常的系统才会失败,但是使用%hhx
或更可移植地编写更干净printf("%x\n", (unsigned)z);
在这个例子中,用by8
而不是Shift7
会更加人为。它在 16-bitint
和 8-bit系统上会有未定义的行为char
。
Adr*_*ica 12
最后一种情况下的“中间”值是(完整的)整数,因此移出原始值“超出范围”的位 unsigned char
保留类型因此当结果转换回单个字节时它们仍会设置。
从这个C11 草案标准:
6.5.7 按位移位运算符
...
3 对每个操作数执行整数提升。结果的类型是提升的左操作数的类型...
但是,在您的第一种情况下,当生成的“完整”整数被转换(即截断)回单个字节时unsigned char tmp = x << 7;
,将tmp
丢失六个“高”位,给出的值为; 当它在 中右移时,结果是(如预期的)。0x80
unsigned char y = tmp >> 7;
0x01
小智 7
没有为这些char
类型定义移位运算符。任何char
操作数的值都被转换为int
,表达式的结果被转换为char
类型。因此,当您将左移和右移运算符放在同一个表达式中时,计算将作为类型执行int
(不丢失任何位),结果将转换为char
.