按位取反(~)的奇怪行为

12

如何解释以下行为?

#include<iostream>

using namespace std;

int main(){

       unsigned char a = 8;

       cerr << "a: " << (int)a << '\n';

       unsigned char b = (~a) >> 6;

       cerr << "b: " << (int)b << '\n';

       unsigned char c = (~a);
       c = c >> 6;

       cerr << "c: " << (int)c << '\n';

       return 0;
}

输出:

a: 8
b: 255
c: 3

进一步测试后发现,(~a) 变成了 int 而不是 unsigned char,这就是为什么会出现 1 的位移。

到底发生了什么?

编辑:我的编译器只是标准的 gcc 4.1.2 版本。

4个回答

23

C语言中所有基本算术和位运算符,如果它们的参数是比较短的整数类型,则会自动将其扩展至至少int类型。这就是语言的定义方式。语言规范将此称为“整型提升”。

(其背后的原因是为了在硬件不支持比完整机器字更短的量的高效操作的体系结构上更容易地实现C语言。当然,这也部分是因为一直以来都是这样工作的,而且不能改变,否则会破坏许多依赖于这种行为的现有代码)。


2
答案中应该提到“促销”这个词。 - unkulunkulu
这个有没有 C 标准的参考资料?我对关于这个的整个章节非常感兴趣。 - Eli Iser
C++03 §5.3.1/9:“~”的操作数必须具有整数或枚举类型;其结果是操作数的补码。将执行整数提升。结果的类型是提升操作数的类型。 - Adam Rosenfield
2
而C99 §6.5.3.3/4中指出:“”运算符的结果是其(提升后的)操作数的按位补码(即,如果转换后的操作数中对应的位未设置,则结果中的每个位都被设置)。操作数进行整数提升,并且结果具有提升类型。如果提升类型是无符号类型,则表达式“E”等效于该类型中可表示的最大值减去“E”。 - Adam Rosenfield
请参阅C99 §6.3转换: 几个运算符会自动将操作数的值从一种类型转换为另一种类型。本子条款指定了这种隐式转换所需的结果,以及由强制转换操作(显式转换)产生的结果。该部分涵盖标准的4页相关内容。 - Jonathan Leffler
这是自由使用掩码有助于防止的行为。例如,unsigned char b = ((~a) & 0xff) >> 6; 标准中有足够的灰色区域和选项,很难记住所有规则,因此通常最好尽可能明确地编写您想要的内容,以消除尽可能多的歧义。 - Richard Chambers

2
~a = 0xFFFFFFF7,因此b = (~a) >> 6的结果为b = 0xFF; 在c的情况下,我们有c = (~a),导致c = 0xF7,因此c>>6将是3。 Henning Makholm在上面很好地解释了整数提升。这篇文章也很有用。

1
a = 8

~a = -9 (int)

~a >> 6 = -1 (int)

(unsigned char)-1 = 255

1
因为这行代码...
unsigned char b = (~a) >> 6;

编译器构建临时变量为signed char类型的(~a),并在右移和LSB字节b后为0xff。但是这几行...
   unsigned char c = (~a);
   c = c >> 6;

构建临时变量不需要编译器,(~a) 的值为0x7f,右移后 c 的值为0x03


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接