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

Why does it make a difference if left and right shift are used together in one expression or not?

我有以下代码:

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

我原以为 yz 是一样的。但它们因是否使用中间变量而有所不同。知道为什么会这样会很有趣。

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

最后一个案例中的 'intermediate' 值是(完整的)整数,因此原始 unsigned char 类型的 'out of range' 移位的位被保留,因此它们仍然是当结果转换回单个字节时设置。

从这个C11 Draft Standard:

6.5.7 Bitwise shift operators
...
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand ...

但是,在您的第一种情况下,unsigned char tmp = x << 7;tmp 在转换结果 'full' 整数时丢失了六个 'high' 位(即 truncated) 返回到单个字节,给出值0x80;当这在 unsigned char y = tmp >> 7; 中右移时,结果是(如预期的那样)0x01.

这个小测试实际上比它看起来更微妙,因为行为是实现定义的:

  • 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 实际上可能传递 intunsigned int 参数。

  • unsigned char tmp = x << 7; 为了计算表达式 x << 7x 作为 unsigned char 首先经历 整数提升 在C标准6.3.3.1中定义:如果一个int可以表示原始类型的所有值(受宽度限制,对于一个位字段),该值将转换为 int;否则,它被转换为 unsigned int。这些称为整数促销。

    所以如果unsigned char中的值位数小于或等于int中的值位数(目前最常见的情况是8 vs 31),则x首先被提升到具有相同值的 int,然后向左移动 7 个位置。结果 0x7f80 保证适合 int 类型,因此行为定义明确,将此值转换为类型 unsigned char 将有效地截断值的高位。如果类型 unsigned char 有 8 位,则值将是 128 (0x80),但如果类型 unsigned char 有更多位,则 tmp 中的值可以是0x1800x3800x7800xf800x1f800x3f80 甚至 0x7f80.

    如果类型 unsigned char 大于 int,这可能发生在 sizeof(int) == 1x 被提升为 unsigned int 和左侧的罕见系统上shift 是在这种类型上执行的。该值是 0x7f80U,它保证适合类型 unsigned int 并将其存储到 tmp 实际上不会丢失任何信息,因为类型 unsigned char 与 [=] 具有相同的大小20=]。所以在这种情况下 tmp 的值为 0x7f80

  • unsigned char y = tmp >> 7;求值同上,tmp根据系统提升为intunsigned int,保留其值,并且这个值右移7个位置,这是完全定义的,因为7小于类型的宽度(intunsigned int)并且值为正数。根据 unsigned char 类型的位数,存储在 y 中的值可以是 13715、[ =78=、63127255,最常见的架构会有y == 1

  • printf("%x\n", y);,最好写成printf("%hhx\n", y);,输出可能是1(最常见的情况)或37f1f3f7fff,具体取决于类型 unsigned char 中的值位数。

  • unsigned char z = (x << 7) >> 7; 如上所述对 x 执行整数提升,然后将值 (255) 左移 7 位作为 intunsigned int,始终生成 0x7f80,然后右移 7 个位置,最终值为 0xff。此行为已完全定义。

  • printf("%x\n", z); 再一次,格式字符串应该是 printf("%hhx\n", z); 并且输出总是 ff.

如今,字节超过 8 位的系统越来越少,但一些嵌入式处理器(例如专用 DSP)仍然这样做。当为 %x 转换说明符传递 unsigned char 时,一个反常的系统会失败,但使用 %hhx 或更便携地写 printf("%x\n", (unsigned)z);[=112 更干净=]

在这个例子中移动 8 而不是 7 会更加人为。它在 16 位 int 和 8 位 char.

的系统上会有未定义的行为