C 位运算符操作的二进制码 (补码)

1. 补码

计算机内存中的每个bit只有两种状态:0或1。计算机内存中,数值一律用补码来表示和存储。无符号数的补码跟原码相同,负数的补码:由负数的原码各位取反(除符号位)后,再加1。


如,值为1类型为char的变量ch1在内存中的二进制码(补码)为:0000 0001。值为-1类型为char的变量ch2在内存中的二进制码(补码)为:1111 1111(-1的原码1000 0001 –>除符号位外各位取反1111 1110 ->再加1得补码1111 1111)。


2. 补码的最高位是否会接受进位,产生进位

Debian GNU/Linux平台下验证。


(1) 有符号数

#include <stdio.h>	

int main(void)
{
        char	ch0, ch1, ch2, ch3;	
        ch0	= 64;//0100 0000
        ch0	+= ch0;//0100 0000 + 0100 0000
        
        ch1	= -1;//1111 1111
        ch1	+= 1;//1111 1111 + 0000 0001
        
        ch2	= 128;//1000 0000
        ch3	= ch2 + ch2;//1000 0000 + 1000 0000
        
        printf("ch0 = %d\tch1 = %d\tch2 = %d\tch3 = %d\n", ch0, ch1, ch2, ch3);
        return 0;
}

编译包含以上内容的文件并执行可执行程序:

lly7@debian:~/C$ gcc DataOverflow.c  -o  DataOverflow

lly7@debian:~/C$ ./DataOverflow

ch0= -128         ch1 = 0     ch2 = -128         ch3= 0
ch1(0000 0000 = 1111 1111 + 0000 0001)及溢出值ch0(1000 0000 = 0100 0000 + 0100 0000)的结果表明补码的符号位接受进位。128的补码1000 0000在char类型下对应的十进制数为-128。ch3的结果表明,补码的符号位在溢出时产生进位。有符号数补码符号位产生进位和接受进位时表示 数据溢出。

(2) 无符号数

#include <stdio.h>

int main(void)
{
        unsigned char	uch1, uch2;
        
        uch1	= 128;//1000 0000
        uch2	= uch1 + uch1;//1000 0000 + 1000 0000
        
        printf("uch2 = %d\n", uch2);
        return 0;
}

编译包含以上内容的文件并执行可执行程序:

lly7@debian:~/C$ gcc DataOverflow.c  -o  DataOverflow

lly7@debian:~/C$ ./DataOverflow

uch2= 0
无符号数的所有位都是数据位,所以可以接受进位。uch2的值表示无符号数最高位可产生进位即溢出。溢出的位去哪里了,丢弃了否,还是进位到了上一个邻近的位中。

3. 位运算符操作的是补码

以移位运算符为例。

#include <stdio.h>

int main(void)
{
        unsigned char 	uch1, uch2;
        char		ch1, ch2;
        
        uch1	= 0x40;//uch1 = 64
        uch2	= 0x80;//uch2 = 128
        
        ch1	= uch1 << 1;
        uch1	= uch1 << 1;
        
        ch2	= uch2 >> 1;
        uch2	= uch2 >> 1;
        
        printf("ch1 = %d\tuch1 = %d\tch2 = %d\tuch2 = %d\n", ch1, uch1, ch2, uch2);
        return 0;
}

在DebianGNU/Linux下用gcc编译包含以上内容的文件并执行可执行程序:

lly7@debian:~/C$gcc BitOperationNotation.c  -o  BitOperationNotation

lly7@debian:~/C$./BitOperationNotation

ch1 =-128         uch1 = 128        ch2 = 64   uch2= 64

lly7@debian:~/C$

(1) 移位后的补码值

由于uch1和uch2的值是无符号数,所以它们在内存中的补码和原码相同,分别为0x40(64)和0x80(128)。ch1在内存中的补码为uch1的补码左移1位(最高位溢出,最低位补0)得0x80,uch1的补码左移一位的值赋给uch1后,uch1的补码也为0x80;ch2在内存中的补码为uch2的补码右移一位(最低位溢出,最高位补0,有的编译器是最低位溢出,最高位补与符号位相同的值)得0x40,uch2补码右移一位的值赋给uch2后,uch2的补码也为0x40。


(2) 补码的访问

值的类型并不是值的内在本质,而是取决于它被使用的方式(访问的指令)无数据转换的情况下,访问二进制序列的指令由编译器关联(编译器根据定义二进制段的类型决定产生何种访问指令)。字符类型其实是具8个位的整型类型。


用printf语句输出(u)ch1,(u)ch2的整型值(%d)时,不涉及数据转换。虽然ch1和uch1在内存中的补码都为0x80,ch2和uch2在内存中的补码都为0x40,但编译器会产生与之数据类型相对应的指令去访问,从而得到不同的输出结果。


编译器根据ch1和ch2的数据类型(char型),产生“字符型(char)访问指令”去访问它们的二进制码,即这两段二进制序列表示有符号char类型值,最高位为符号位,其余位为数据位。所以,ch1和ch2的十进制值分别为-128和64。编译器根据uch1和uch2的数据类型(unsigned char),产生“无符号字符(unsignedchar)型访问指令”去访问它们的二进制码,即这两段二进制序列表示无符号字符类型数据,无符号位。所以,uch1和uch2的十进制值分别为128和64。


移位运算符不需要两个操作数的类型一致,但两边操作数都要做Integer Promotion,整个表达式的类型和左操作数提升后的类型相同。当操作数是有符号数时,右移运算的规则比较复杂:如果是正数,那么高位移入0。如果是负数,那么高位移入1还是0不一定,这是Implementation-defined的。对于x86平台的gcc编译器,最高位移入1,也就是仍保持负数的符号位,这种处理方式对负数仍然保持了“右移1位相当于除以2”的性质。


C Note Over.

你可能感兴趣的:(C 位运算符操作的二进制码 (补码))