如何在C中评估轮班操作员



最近,当我使用移位>> <<执行操作时,我注意到了一个(奇怪的)行为!

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

#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 = 5b = 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逐位移位运算符:

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

在C99和C90中也有类似的说法。

最新更新