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
我会期望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小于类型 ( intor unsigned int)的宽度,并且值为正。根据类型的位数,unsigned char存储在中的值y可以是1, 3, 7, 15, 31, 63, 127or 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丢失六个“高”位,给出的值为; 当它在 中右移时,结果是(如预期的)。0x80unsigned char y = tmp >> 7;0x01
小智 7
没有为这些char类型定义移位运算符。任何char操作数的值都被转换为int,表达式的结果被转换为char类型。因此,当您将左移和右移运算符放在同一个表达式中时,计算将作为类型执行int(不丢失任何位),结果将转换为char.