为什么在一个表达式中,左移和右移是否一起使用会有区别?

问题描述 投票:1回答:1

我有以下代码。

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 是一样的。但它们的不同取决于是否使用了中间变量。我很想知道为什么会出现这种情况。

c operators bit-shift
1个回答
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 可能会通过 intunsigned int 参数。

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

    所以如果在 unsigned char 小于或等于 int 目前最常见的情况是8对31)。x 首先晋升为 int 的数值,然后将其向左移动 7 阵地。结果是: 0x7f80,保证适合在 int 类型,所以行为是很好定义的,将这个值转换为类型的 unsigned char 将有效地截断该值的高阶位。如果类型为 unsigned char 有8位,该值将是 128 (0x80),但如果类型 unsigned char 有更多的位数,在 tmp 可以 0x180, 0x380, 0x780, 0xf80, 0x1f80, 0x3f80 甚至 0x7f80.

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

  • unsigned char y = tmp >> 7; 评价程序与上述相同。tmp 晋升为 intunsigned int 根据系统的不同,保留了它的值,这个值向右移动了7个位置,这是完全确定的,因为 7 小于类型的宽度(intunsigned int),且该值为正值。根据类型的位数 unsigned char的值,存储在 y 可以 1, 3, 7, 15, 31, 63, 127255,最常见的架构将有 y == 1.

  • printf("%x\n", y); 同样,最好是写 printf("%hhx\n", y); 而输出可以是 13, 7, f, 1f, 3f, 7fff 取决于类型中的值位数 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还是会这样做。要想在通过一个反常的系统时失败,就必须要有一个反常的系统。unsigned char 对于 %x 转换指定器,但更简洁的做法是使用 %hhx 或者更方便地写成 printf("%x\n", (unsigned)z);

转移 8 而不是 7 在这个例子中,它将会更加造作。它在16位的系统上会有未定义的行为。int 和8位 char.


13
投票

在你的最后一个例子中,"中间 "值是(完整的)整数,所以被移位的位 "超出了 "原来的范围。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.

© www.soinside.com 2019 - 2024. All rights reserved.