为什么在一个表达式中同时使用左移和右移会有所不同?

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 << 7x作为一个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可以是0x1800x3800x7800xf800x1f800x3f80甚至是0x7f80

    如果类型unsigned char大于int,这可能发生在罕见的系统上sizeof(int) == 1,其中,x被提升到unsigned int并在此类型上执行左移。值 is 0x7f80U,它保证适合类型unsigned int并且存储它tmp实际上不会丢失任何信息,因为类型unsigned charunsigned int. 所以在这种情况下tmp会有价值0x7f80

  • unsigned char y = tmp >> 7;求值过程同上,tmp被提升到intunsigned 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(最常见的情况),或者37f1f3f7fff根据在类型值的位的数目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

  • @FredLarson你绝对不会看到无符号类型的符号扩展。至于它升级到的内容,根据 C11 草案标准第 6.3.1.1 节,它升级为 `int`(假设 `int` 在所述系统上大于 `char`):“*如果 **int** 可以表示原始类型的所有值(对于位域,受宽度限制),该值将转换为 **int**;否则,它将转换为 **unsigned int**。* (5认同)

小智 7

没有为这些char类型定义移位运算符。任何char操作数的值都被转换为int,表达式的结果被转换为char类型。因此,当您将左移和右移运算符放在同一个表达式中时,计算将作为类型执行int(不丢失任何位),结果将转换为char.