24

我最近在使用 shift 进行操作时注意到了一个(奇怪的)行为>> <<

为了解释它,让我编写这个小的可运行代码,它执行两个应该是相同的操作(在我的理解中),但我对不同的结果感到惊讶!

#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=%X\n", a, b);
    return 0;
} 

跑的时候,a = 5b = 1。我希望它们都等于1!有人可以解释为什么我得到这样的结果吗?

PS:在我的环境中,大小unsigned char为 1 字节

4

3 回答 3

31

In the first example:

  • a is converted to an int, shifted left, then right and then converted back to usigned char.

This will result to a=5 obviously.

In the second example:

  • b is converted to int, shifted left, then converted back to unsigned char.
  • b is converted to int, shifted right, then converted back to unsigned char.

The difference is that you lose information in the second example during the conversion to unsigned char

于 2014-09-23T07:31:37.410 回答
17

行之间发生的事情的详细说明:

案例一:

  • 在表达式a = ((a<<7)>>7);中,a<<7首先计算。
  • C 标准规定,移位运算符的每个操作数都是隐式整数提升的,这意味着如果它们是 bool、char、short 等类型(统称为“小整数类型”),它们将被提升为int.
  • 这是 C 中几乎每个运算符的标准做法。移位运算符与其他运算符的不同之处在于它们不使用另一种常见的隐式提升,称为“平衡”。相反,移位的结果总是具有提升的左操作数的类型。在这种情况下int
  • 所以a被提升为 type int,仍然包含值 0x05。7文字已经是类型,int所以它不会被提升。
  • 当你左移int7 时,你得到 0x0280。操作的结果是类型int
  • 请注意,这int是一个有符号类型,因此如果您继续将数据进一步移动到符号位中,您将调用未定义的行为。同样,如果左操作数或右操作数是负值,您也会调用未定义的行为。
  • 您现在有了表达式 a = 0x280 >> 7;。因为两个操作数都已经是 int,所以下一个班次操作不会发生任何提升。
  • 结果是 5 和 int 类型。然后将此 int 转换为 unsigned char,这很好,因为结果足够小以适应。

案例二:

  • b <<= 7;相当于b = b << 7;
  • 和以前一样,b晋升为int. 结果将再次为 0x0280。
  • 然后,您尝试将此结果存储在无符号字符中。它不适合,因此它将被截断以仅包含最低有效字节0x80
  • 在下一行,b再次提升为包含 0x80 的 int。
  • 然后将 0x80 移动 7,得到结果 1。这是 int 类型,但可以放入 unsigned char,因此它适合 b。

好建议:

  • 永远不要对有符号整数类型使用按位运算符。这在 99% 的情况下没有任何意义,但会导致各种错误和定义不明确的行为。
  • 使用按位运算符时,请使用 C 中的类型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=%X\n", a, b);
    return 0;
} 
于 2014-09-23T09:03:53.377 回答
14

The shift operations would do integer promotions to its operands, and in your code the resulting int is converted back to char like this:

// 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);

Quote from the N1570 draft (which became the standard of C11 later):

6.5.7 Bitwise shift operators:

  1. Each of the operands shall have integer type.
  2. The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

And it's supposed that in C99 and C90 there are similar statements.

于 2014-09-23T07:31:39.173 回答