KEEP K.I.S.S.

tk's blog

C 语言中的位取反操作符 续

前一篇文章的问题,在我查阅了《The C Programming Language》(K&R) 第二版后,发现了如下的定义

在附录A 参考手册的 A.7.4 一元运算符 二进制反码运算符 中有如下表述

一元运算符 ~ 的操作数必须是整型,结果为操作数的二进制反码。在运算过程中需要对操作数进行整形提升。如果操作数为无符号类型,则结果为提升后的类型能够表示的最大值减去操作数的值而得到的结果值。如果操作数为带符号型,则结果的计算方式为:将提升后的操作数转换为相应的无符号类型,使用运算符 ~ 计算反码,再将结果转换为带符号的类型结果的类型为提升后的操作数类型

对于整形提升,在附录A 参考手册的 A6.1 整形提升 有如下表述

在一个表达式中,凡是可以使用整形的地方都可以使用带符号或无符号的字符、短整型或整型位字段,还可以使用枚举类型的对象。如果原始类型的所有值都可用 int 类型表示,则其值将被转换为 int 类型;否则将被转换为 unsigned int 类型。这一过程称为整形提升(integral promotion)。

请注意红色标注的句子,“结果的类型为提升后的操作数类型”,意味着 ~(char) 、 ~(short) 等的结果类型都是整型(int)。

C 语言中的位取反操作符

话不多说,先来看一段代码:

 

 1 #include <stdio.h>
 2 
 3 int main(int argc, char *argv[])
 4 {
 5         unsigned char uc = 0x1f;
 6         unsigned int ui = ~uc;
 7         unsigned long long ull = ~uc;
 8 
 9         unsigned char cc = ~uc;
10         printf("uc = %#x\n", uc);
11         printf("cc: ~uc = %#x\n", cc);
12         printf("ui: ~uc = %#x\n", ui);
13         printf("ull: ~uc = %#llx\n", ull);
14         return 0;
15 }

因为用到了 C99 标准的 long long 类型,所以编译时候需要添加 "-std=c99" 选项:

gcc -Wall -std=c99 -o file file.c

输出结果(x86 mingw32):

 

uc = 0x1f
cc: ~uc = 0xe0
ui: ~uc = 0xffffffe0
ull: ~uc = 0xffffffffffffffe0
 
这里显现出了 位取反 操作的结果特殊性,对于通常而言,我们以为一个 unsigned char 位取结果的类型也应该是 unsigned char ,也就是说当我们把这个结果赋给一个 unsigned int (或者 unsigned long long)类型的值时候,这个值也应该是 不会超过 unsigned char 的取值范围的。但是上述代码显示出并非这样的情况。
 
上述代码显示出把 unsigned char 位取反值赋给 unsigned int 变量时,除了低位字节是补码外,高位字节都是 0 的补码 1了。对于赋值给 unsigned long long 变量也是如此。
 
我个人理解的解释是,在 x86-32 的机器上,机器字长为 32,寄存器长度也是32,对于像 unsigned char 这样的 1 byte 的值,运算时会存储在寄存器中的低位字节,高位字节都为0,然后位取反操作指令会对整个寄存器的值位取反,然后赋值给 unsigned int (4 bytes)这样的变量时,是将整个寄存器值取出。这样高位字节都会是 0 的补码 1。但是这样怎么解释赋值给 unsigned long long 类型变量发生的情况呢?或者说对于位取反操作的结果是个特殊状态的值?
 
PS: 最近在看 CSAPP(《深入理解计算机系统》)第二版,其中在做练习题 2.12 发现了上述情形,之前不知道,所以就研究了一下下,不过还是半解。。。。