这篇文章是 CSAPP 的第二章的学习笔记,内容为数据的表示和操作。计算机使用二进制来表示数据,1位的数据通常没有什么作用,因此我们需要把许多位组合起来进行表示,例如,对于数字我们有无符号整数表示、补码表示、浮点数表示方法,同时我们还要考虑溢出、符号等问题。
数据的存储
计算机不是一位一位对数据进行操作的,一般都是以 byte (8 bits) 作为单位,这是内存最小的寻址单位。内存中的每个 byte 都有一个标识作为它的地址(虚拟地址空间)
进制表示
二进制、十进制、十六进制之间的转换如下表所示:
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
十六进制 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|
十进制 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
二进制 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
字长
通常机器字长决定内存的寻址空间,我们所听到的32位计算机、64位计算机,指的就是机器字长(32位计算机可寻址的内存大小为4GB),另外通常机器字长就是 C 语言中指针的大小。
数据的大小
在 C 语言中,不同的数据类型的大小是不同的,并且不同字长的机器相同的数据类型有可能大小也是不同的,例如:
C 语言数据类型 | 32位机器 | 64位机器 |
---|---|---|
char | 1 | 1 |
short int | 2 | 2 |
int | 4 | 4 |
long int | 4 | 8 |
long long int | 8 | 8 |
char * | 4 | 8 |
flout | 4 | 4 |
double | 8 | 8 |
寻址顺序和字节顺序
大端和小端(Big endian & Little endian)
以存储『0x01234567』为例:
大端:
小端:
存在的问题
大端、小端存储顺序的不同造成了不同计算机之间进行的网络传输存在问题,因此在网络传输的过程中需要双方计算机了解互相的数据是采用大端还是小端方式,然后将接收到的数据按照自己的数据字节顺序进行表示。
字符串的表示
在 C 语言中,英文字符串的表示 通常是:ASCII 码 + ‘\0’,例如,字符串”12345”通常编码为:『31 32 33 34 35 00』
布尔代数
非
~ | |
---|---|
0 | 1 |
1 | 0 |
与
& | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
或
\ | 0 | 1 | |
---|---|---|---|
0 | 0 | 1 | |
1 | 1 | 1 |
异或
^ | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
C 语言中的逻辑操作
C语言中的逻辑操作包括:逻辑与(&&)、逻辑或(||)、逻辑非(!)
下面直接看一个例子:
1 | 表达式 | 值 | 表达式 | 值 | |
C 语言中的移位操作
对于移位操作,通常有两种:算数移位和逻辑移位,对于右移操作分为算术右移和逻辑右移;对于所有的左移操作我们都认为它是逻辑左移。
规则为:
左移操作:低位补0,高位移出。
逻辑右移:高位补0,低位移出。
算数右移:若高位为0,高位补0,低位移出;若高位为1,高位补1,低位移出。
我们来看一个例子:
x | x << 3 | x >> 2(逻辑) | x >> 2(算数) |
---|---|---|---|
1100 0011 | 0001 1000 | 0011 0000 | 1111 0000 |
0111 0101 | 1010 1000 | 0001 1101 | 0001 1101 |
整数的表示
无符号整数的表示
无符号整数的表示在编码上就相当于从十进制直接转换为二进制。公式如下:
其中,B2U
表示从二进制(Binary)转化为无符号整数(Unsigned interger),w
表示总共用多少位来表示。
补码表示
补码表示法将最高位用作符号位,公式如下:
例:
类型转换中的扩展与截取
- 扩展(从 short 到 int)
- 无符号数:直接高位补0
- 有符号数:补符号位
- 截取(int 到 short):对于小的数正常截取
- 无符号数:mode 操作
- 有符号数:近似 mode 操作
整数的运算
无符号加法
对于无符号加法,两个 w 位的数字相加,结果有可能是 w+1 位,对于这种情况,我们会舍弃最高位,也就是发生了溢出。
补码加法
补码加法操作方式和无符号加法是一致的,只不过会发生两种溢出:正溢出和负溢出。
正溢出就是原来的符号位为0,但是由于相加的数字过大,导致了符号位变成了1,结果变成了负数;
负溢出就是原来符号位为1,但是由于相加的数字过大导致进位,和符号位相加后为0,原本的负数变为了正数。
浮点数的表示
一般表示
浮点数表示的公式为:
十进制 | 二进制 |
---|---|
5+4/3 | 101.11 |
2+7/8 | 10.111 |
1+7/16 | 1.0111 |
我们发现,这种表示方法有限制:只有形如 $\frac { x }{ { 2 }^{ i } } $ 的小数部分可以精确表示。
IEEE 表示
在 IEEE 的标准中,使用以下公式来表示浮点数:
$$
V={ \left( -1 \right) }^{ s }\times M\times { 2 }^{ E }
$$
其中,s 表示符号位,M 通常表示一个1.0~2.0的小数,E表示2的次方数。
具体编码形式如下:
示例
在这个示例中我们采用 1 位符号位,4 位 exp,3 位 frac,对应的 bias 就是 2^(4-1)-1=7,
对于规范化数:e = Exp - bias;
对于非规范化数:e = 1 - bias;
s | exp | frac | e | 值 | 备注 |
---|---|---|---|---|---|
0 | 0000 | 000 | -6 | 0 | 非规范化数值 |
0 | 0000 | 001 | -6 | (1/64)*(1/8)=1/512 | 非规范化数值 |
0 | 0000 | 010 | -6 | (1/64)*(2/8)=2/512 | 非规范化数值 |
0 | 0000 | 011 | -6 | (1/64)*(3/8)=3/512 | 非规范化数值 |
…… | …… | …… | …… | …… | …… |
0 | 0000 | 111 | -6 | (1/64)*(7/8)=7/512 | 能表示的最大非规范化数值 |
0 | 0001 | 000 | -6 | (1/64)*(8/8)=1/64 | 能表示的最小规范化数值 |
0 | 0001 | 001 | -6 | (1/64)*(9/8)=9/512 | 规范化数值 |
…… | …… | …… | …… | …… | …… |
0 | 0110 | 110 | -1 | (1/2)*(14/8)=7/8 | 规范化数值 |
0 | 0110 | 111 | -1 | (1/2)(15/8)=15/16 | 最接近1的值 |
0 | 0111 | 000 | 0 | 1*8/8=1 | 1 |
0 | 0111 | 001 | 0 | 1*9/8=9/8 | 大于1最接近1的值 |
…… | …… | …… | …… | …… | …… |
0 | 1110 | 111 | 7 | 128*15/8=240 | 能表示的最大的规范化数值 |
0 | 1111 | 000 | - | 正无穷 | 正无穷 |
本文的版权归作者 罗远航 所有,采用 Attribution-NonCommercial 3.0 License。任何人可以进行转载、分享,但不可在未经允许的情况下用于商业用途;转载请注明出处。感谢配合!