为什么在一个表达式中是否同时使用左移和右移会有所不同?
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
我原以为 y
和 z
是一样的。但它们因是否使用中间变量而有所不同。知道为什么会这样会很有趣。
没有为 char
类型定义移位运算符。任何 char
操作数的值都被转换为 int
并且表达式的结果被转换为 char
类型。
因此,当您将左右移位运算符放在同一个表达式中时,计算将按类型 int
执行(不丢失任何位),结果将转换为 char
.
最后一个案例中的 'intermediate' 值是(完整的)整数,因此原始 unsigned char
类型的 'out of range' 移位的位被保留,因此它们仍然是当结果转换回单个字节时设置。
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
实际上可能传递 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 vs 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
和左侧的罕见系统上shift 是在这种类型上执行的。该值是 0x7f80U
,它保证适合类型 unsigned int
并将其存储到 tmp
实际上不会丢失任何信息,因为类型 unsigned char
与 [=] 具有相同的大小20=]。所以在这种情况下 tmp
的值为 0x7f80
。
unsigned char y = tmp >> 7;
求值同上,tmp
根据系统提升为int
或unsigned int
,保留其值,并且这个值右移7个位置,这是完全定义的,因为7
小于类型的宽度(int
或unsigned int
)并且值为正数。根据 unsigned char
类型的位数,存储在 y
中的值可以是 1
、3
、7
、15
、[ =78=、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)仍然这样做。当为 %x
转换说明符传递 unsigned char
时,一个反常的系统会失败,但使用 %hhx
或更便携地写 printf("%x\n", (unsigned)z);
[=112 更干净=]
在这个例子中移动 8
而不是 7
会更加人为。它在 16 位 int
和 8 位 char
.
的系统上会有未定义的行为
我有以下代码:
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
是一样的。但它们因是否使用中间变量而有所不同。知道为什么会这样会很有趣。
没有为 char
类型定义移位运算符。任何 char
操作数的值都被转换为 int
并且表达式的结果被转换为 char
类型。
因此,当您将左右移位运算符放在同一个表达式中时,计算将按类型 int
执行(不丢失任何位),结果将转换为 char
.
最后一个案例中的 'intermediate' 值是(完整的)整数,因此原始 unsigned char
类型的 'out of range' 移位的位被保留,因此它们仍然是当结果转换回单个字节时设置。
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
实际上可能传递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 vs 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
和左侧的罕见系统上shift 是在这种类型上执行的。该值是0x7f80U
,它保证适合类型unsigned int
并将其存储到tmp
实际上不会丢失任何信息,因为类型unsigned char
与 [=] 具有相同的大小20=]。所以在这种情况下tmp
的值为0x7f80
。unsigned char y = tmp >> 7;
求值同上,tmp
根据系统提升为int
或unsigned int
,保留其值,并且这个值右移7个位置,这是完全定义的,因为7
小于类型的宽度(int
或unsigned int
)并且值为正数。根据unsigned char
类型的位数,存储在y
中的值可以是1
、3
、7
、15
、[ =78=、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)仍然这样做。当为 %x
转换说明符传递 unsigned char
时,一个反常的系统会失败,但使用 %hhx
或更便携地写 printf("%x\n", (unsigned)z);
[=112 更干净=]
在这个例子中移动 8
而不是 7
会更加人为。它在 16 位 int
和 8 位 char
.