我有以下代码。
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
是一样的。但它们的不同取决于是否使用了中间变量。我很想知道为什么会出现这种情况。
这个小测试实际上比看起来更微妙,因为行为是由实现定义的。
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
. 这些称为整数推广。
所以如果在 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
晋升为 int
或 unsigned int
根据系统的不同,保留了它的值,这个值向右移动了7个位置,这是完全确定的,因为 7
小于类型的宽度(int
或 unsigned int
),且该值为正值。根据类型的位数 unsigned char
的值,存储在 y
可以 1
, 3
, 7
, 15
, 31
, 63
, 127
或 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位作为一个 int
或 unsigned 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
.
在你的最后一个例子中,"中间 "值是(完整的)整数,所以被移位的位 "超出了 "原来的范围。unsigned char
类型被保留,因此当结果被转换回单字节时,它们仍然被设置。
由此可见 C11 标准草案:
6.5.7 位移运算符 ... 3 对每个操作数进行整数推广。结果的类型是被推广的左操作数的类型......。
然而,在你的第一种情况下。unsigned char tmp = x << 7;
జజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజజ tmp
当转换结果为 "全 "整数时,会丢失六个 "高 "位(即 截断)返回到一个单一的字节,给出一个值为 0x80
;当这时,再将其右移到 unsigned char y = tmp >> 7;
,结果是 0x01
.
的移位运算符没有定义。char
类型。任何 char
操作数转换为 int
并将表达式的结果转换为 char
所以,当你把左移和右移运算符放在同一个表达式中时,计算将以类型的方式进行。int
(在不损失任何比特的情况下),结果将被转换为 char
.