最近,当我使用移位>>
<<
执行操作时,我注意到了一个(奇怪的)行为!
为了解释它,让我写一个可运行的小代码,它执行两个本应相同的操作(在我的理解中),但我对不同的结果感到惊讶!
#include <stdio.h>
int main(void) {
unsigned char a=0x05, b=0x05;
// first operation
a = ((a<<7)>>7);
// second operation
b <<= 7;
b >>= 7;
printf("a=%X b=%Xn", a, b);
return 0;
}
运行时,a = 5
和b = 1
。我希望它们都等于1!有人能解释一下我为什么会得到这样的结果吗?
p.S:在我的环境中,unsigned char
的大小是1字节
在第一个示例中:
a
被转换为int
,向左、然后向右移位,然后被转换回usigned char
这将明显导致CCD_ 9。
在第二个例子中:
b
被转换为int
,向左移位,然后被转换回unsigned char
b
被转换为int
,向右移位,然后被转换回unsigned char
不同之处在于,在第二个示例中,在转换为unsigned char
的过程中丢失了信息
对行与行之间发生的事情的详细解释:
案例a:
- 在表达式
a = ((a<<7)>>7);
中,首先评估a<<7
- C标准规定移位运算符的每个操作数都是隐式整数提升的,这意味着如果它们的类型为bool、char、short等(统称为"小整数类型"),则它们将被提升为
int
- 这几乎是C中每一位操作员的标准做法。轮班操作员与其他操作员的不同之处在于,他们没有使用另一种常见的、隐含的晋升方式,即"平衡"。相反,移位的结果总是具有提升的左操作数的类型。在这种情况下,
int
- 因此
a
被提升为类型int
,仍然包含值0x05。7
文本的类型已为int
,因此不会升级 - 当你把这个
int
左移7时,你得到0x0280。运算的结果属于int
类型 - 请注意,
int
是一个有符号的类型,所以如果您继续将数据进一步移位到符号位中,您就会调用未定义的行为。类似地,如果左操作数或右操作数为负值,您也会调用未定义的行为 - 现在可以得到表达式a=
0x280 >> 7;
。由于两个操作数都已为int,因此不会对下一个移位操作进行提升 - 结果是5,类型为int。然后将此int转换为无符号字符,这很好,因为结果足够小,可以容纳
案例b:
- CCD_ 29相当于CCD_
- 和以前一样,
b
被提升为int
。结果将再次为0x0280 - 然后,您尝试将此结果存储在一个未签名的字符中。它将不适合,因此它将被截断为只包含最低有效字节
0x80
- 在下一行中,
b
再次被提升为int,包含0x80 - 然后将0x80移位7,得到结果1。这是int类型的,但可以放在无符号字符中,所以它可以放在b中
好建议:
- 永远不要在有符号整数类型上使用逐位运算符。这在99%的情况下没有任何意义,但可能会导致各种错误和定义不清的行为
- 使用逐位运算符时,请使用
stdint.h
中的类型,而不是C中的基元默认类型 - 在使用位运算符时,请使用对预期类型的显式强制转换,以防止出现错误和意外的类型更改,但也要明确您实际上了解隐式类型提升是如何工作的,并且您并不是偶然地让代码工作的
编写程序的更好、更安全的方法是:
#include <stdio.h>
#include <stdint.h>
int main(void) {
uint8_t a=0x05;
uint8_t b=0x05;
uint32_t tmp;
// first operation
tmp = (uint32_t)a << 7;
tmp = tmp >> 7;
a = (uint8_t)tmp;
// second operation
tmp = (uint32_t)b << 7;
tmp = tmp >> 7;
b = (uint8_t)tmp;
printf("a=%X b=%Xn", a, b);
return 0;
}
移位操作将对其操作数进行整数提升,在代码中,生成的int
将转换回char
,如下所示:
// first operation
a = ((a<<7)>>7); // a = (char)((a<<7)>>7);
// second operation
b <<= 7; // b = (char) (b << 7);
b >>= 7; // b = (char) (b >> 7);
引用N1570草案(后来成为C11的标准):
6.5.7逐位移位运算符:
- 每个操作数应具有整数类型
- 对每个操作数执行整数提升。结果的类型是提升后的左操作数。如果右操作数的值为负数或大于或等于提升后的左操作数的宽度,则行为未定义
在C99和C90中也有类似的说法。