如何在C中评估换档操作符?
当我使用shift >>
<<
进行操作时,我最近发现了一个(奇怪的)行为!
为了解释它,让我编写这个小的可运行代码,执行两个应该是相同的操作(在我的理解中),但我对不同的结果感到惊讶!
#include int main(void) { unsigned char a=0x05, b=0x05; // first operation a = ((a<>7); // second operation b <>= 7; printf("a=%X b=%X\n", a, b); return 0; }
运行时, a = 5
, b = 1
。 我希望它们都等于1! 有人可以解释为什么我得到这样的结果?
PS:在我的环境中, unsigned char
的大小是1个字节
shift操作会对其操作数进行整数提升,并且在您的代码中,生成的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中有类似的陈述。
在第一个例子中:
-
a
转换为int
,向左移动,然后向右移动,然后转换回usigned char
。
这将导致a=5
显然。
在第二个例子中:
-
b
转换为int
,向左移动,然后转换回unsigned char
。 -
b
转换为int
,向右移位,然后转换回unsigned char
。
区别在于您在转换为unsigned char
期间丢失了第二个示例中的信息
对行之间发生的事情的详细解释:
案例a:
- 在表达式
a = ((a<<7)>>7);
,首先评估a<<7
。 - C标准规定移位运算符的每个操作数都是隐式整数提升的,这意味着如果它们是bool,char,short等类型(统称为“小整数类型”),它们将被提升为
int
。 - 这是C中几乎每个操作员的标准做法。使移位操作员与其他操作员不同的是,他们不使用另一种称为“平衡”的常见隐式促销。 相反,shift的结果总是具有提升的左操作数的类型。 在这种情况下
int
。 - 所以
a
被提升为int
类型,仍然包含值0x05。7
文字已经是int
类型,所以它没有得到提升。 - 当你把这个
int
移位7时,你得到0x0280。 操作的结果是int
类型。 - 请注意,
int
是一个带符号的类型,所以如果你继续将数据转移到符号位,你就会调用未定义的行为。 同样,如果左侧或右侧操作数为负值,您还将调用未定义的行为。 - 你现在有了表达式a =
0x280 >> 7;
。 由于两个操作数都已经为int,因此下一次转换操作不会进行任何促销。 - 结果是5,类型为int。 然后将此int转换为unsigned char,这很好,因为结果足够小以适应。
案例b:
-
b <<= 7;
相当于b = b << 7;
。 - 和以前一样,
b
被提升为int
。 结果将再次为0x0280。 - 然后尝试将此结果存储在unsigned char中。 它将不适合,因此它将被截断为仅包含最低有效字节
0x80
。 - 在下一行,
b
再次被提升为一个包含0x80的int。 - 然后你将0x80移动7,得到结果1.这是int类型,但可以放在unsigned char中,所以它适合b。
好建议:
- 永远不要在有符号整数类型上使用逐位运算符。 这在99%的案例中没有任何意义,但可能导致各种错误和定义不明确的行为。
- 使用按位运算符时,请使用
stdint.h
的类型而不是C中的原始默认类型。 - 使用按位运算符时,使用显式强制类型转换为预期的类型,以防止错误和意外的类型更改,但也要明确您实际了解隐式类型促销如何工作,并且您不仅仅使代码工作意外地。
编写程序的更好,更安全的方法是:
#include #include 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=%X\n", a, b); return 0; }