如何在C中评估换档操作符?

cho*_*aib 24 c bit-shift integer-promotion

当我使用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;
} 
Run Code Online (Sandbox Code Playgroud)

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

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

thu*_*eys 31

在第一个例子中:

  • a转换为a int,向左移动,然后向右移动,然后转换回usigned char.

a=5显然会导致.

在第二个例子中:

  • b转换为int,向左移动,然后转换回unsigned char.
  • b转换为int,右移,然后转换回unsigned char.

不同之处在于您在转换为第二个示例时丢失了信息 unsigned char

  • `a =((char)a << 7)>> 7;`将这样做(移位,截断然后向后移动)(注意你的括号).虽然更容易做'a = a&0x01;` (5认同)
  • @Baldrickk @thumbmunkeys:强制转换为`char`会破坏它所有的'0xFF`所以我想它更适合强制转换为`unsigned char`. (2认同)
  • @chouaib更好地用`a = a&0x01掩盖它;`在可用的时候点你想做的操作,而不是利用系统的'怪癖',除非出于某种原因你想故意混淆你的代码. (2认同)

Lun*_*din 17

对行之间发生的事情的详细解释:

案例a:

  • 在表达式中a = ((a<<7)>>7);,a<<7首先进行评估.
  • C标准规定移位运算符的每个操作数都是隐式整数提升的,这意味着如果它们是bool,char,short等类型(统称为"小整数类型"),它们会被提升为int.
  • 这是C中几乎每个操作员的标准做法.使移位操作员与其他操作员不同的是,他们不使用另一种称为"平衡"的常见隐式促销.相反,shift的结果总是具有提升的左操作数的类型.在这种情况下int.
  • 因此a被提升为类型int,仍然包含值0x05.该7文本已经类型的int,因此不会得到提升.
  • 当你离开int7时,你得到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%的案例中没有任何意义,但可能导致各种错误和定义不明确的行为.
  • 使用按位运算符时,请使用C中的类型stdint.h而不是原始默认类型.
  • 使用按位运算符时,使用显式强制类型转换为预期的类型,以防止错误和意外的类型更改,但也要清楚地表明您实际了解隐式类型促销如何工作,并且您不仅仅使代码工作意外地.

编写程序的更好,更安全的方法是:

#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;
} 
Run Code Online (Sandbox Code Playgroud)


sta*_*ify 14

移位操作会对其操作数进行整数提升,并且在您的代码中,结果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);
Run Code Online (Sandbox Code Playgroud)

引用N1570草案(后来成为C11的标准):

6.5.7按位移位运算符:

  1. 每个操作数应具有整数类型.
  2. 对每个操作数执行整数提升.结果的类型是提升的左操作数的类型.如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义.

并且假设在C99和C90中有类似的陈述.

  • Char**是一个整数类型. (3认同)