我有以下代码:
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
.
关于c - 为什么在一个表达式中同时使用左移和右移会有所不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61958789/