深入理解计算机系统(二)——数据的存储和表示
-本章主要分析无符号数,有符号数,浮点数的二进制表示
1.信息存储
- 大多数计算机将8位的块byte作为最小的寻址单位
- 机器级的程序将内存视为一个数组,即虚拟内存
- 将所有可能地址的集合称为虚拟地址空间
- C语言中的指针就是某存储块第一个字节的虚拟地址
(1)16进制表示法
(这部分挺简单的所以略啦23333333)
(2)字数据大小
机器的字长:指明了指针数据的标称大小,即,地址由机器字长的一个字编码,所以对于一个字长为
w
w
w为的机器而言,虚拟地址的范围为
0
∼
2
w
−
1
0 \sim2^w-1
0∼2w−1,程序最多访问
2
w
2^w
2w个字节。
例如,32位机器(虚拟地址空间4GB),64位机器(虚拟地址空间16EB)
有些数据类型在32位机器和64位机器上大小不相同,在使用时应注意可移植性(如,用int存储地址在64位机器上会导致错误)
1 K = 2 10 1 M = 2 20 1 G = 2 30 1 T = 2 40 1 P = 2 50 1 个 二 进 制 位 : 1 b i t 8 个 二 进 制 位 : 1 b y t e ( 字 节 ) 16 个 二 进 制 位 : 1 w o r d ( 字 ) ( x 86 ) 1K = 2^{10}\\ 1M = 2^{20}\\ 1G = 2^{30}\\ 1T = 2^{40}\\ 1P = 2^{50}\\ 1个二进制位:1bit\\ 8个二进制位: 1byte(字节)\\ 16个二进制位:1word(字)(x86) 1K=2101M=2201G=2301T=2401P=2501个二进制位:1bit8个二进制位:1byte(字节)16个二进制位:1word(字)(x86)
有符号 | 无符号 | 32位 | 64位 |
---|---|---|---|
char | unsigned char | 1 | 1 |
short | unsigned short | 2 | 2 |
int | unsigned int | 4 | 4 |
long | unsigned long | 4 | 8 |
int32_t | uint32_t | 4 | 4 |
int64_t | uint64_t | 8 | 8 |
char* | 4 | 8 | |
float | 4 | 4 | |
double | 8 | 8 |
C99提供了一类固定的数据类型,数据大小是固定的即int32_t和int64_t
大部分数据类型都编码为有符号数值
(3)寻址和字节顺序
- 多字节对象的存储:连续存储
- 两种规则:小端法,大端法
小端法:最低有效位在前边(高位高址);
大端法:最高有效为在前边(高位低址)
双端法:硬件兼容大端或小端,但是选择了特定操作系统,端就确定了下来 - 字节顺序会成为问题的情况
①不同机器间通过网络传输二进制数据
②阅读表示整数的数据的字节序列
③编写规避正常的类型系统的程序时(利用强转或联合强制用不同类型的数据引用)
以下代码用强制类型转换来访问并打印不同程序对象的字节表示
#include <stdio.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start,size_t len)
{
size_t i;
for(i = 0;i <len ;i++)
{
printf(" %.2x",start[i]);
}
printf("\n");
}
void show_int(int x)
{
show_bytes((byte_pointer) &x,sizeof(int));
}
void show_float(float x)
{
show_bytes((byte_pointer) &x,sizeof(float));
}
void show_pointer(void *x)
{
show_bytes((byte_pointer) &x,sizeof(void *));
}
void test_show_bytes(int val)
{
int ival = val;
float fval = (float)val;
int *pval = &ival;
show_int(ival);
show_float(fval);
show_pointer(pval);
}
int main()
{
int val = 12345;
test_show_bytes(val);
return 0;
}
如图展示了int 类型的3510593(0x00359141)和float类型的3510593.0(0x4A564504)的二进制数的匹配情况
(4)表示字符串
ASCII 码表示,’\0’结尾,数字x的十六进制表示应当恰好为0x3x 如3即为0x33,而结尾则是0x00
(5)表示代码
- 二进制代码是不兼容的,二进制代码很少能在不同的机器和操作系统组合间移植
- 机器角度看,程序仅仅是字节序列,除了辅助表外,很少有源程序的信息。
(6)布尔代数简介
(略)
(7)C语言中的位级运算
(略)
- 关于与:与0必为0,与1不变,置0用
- 关于或:或1必为1,或0不变,置1用
- 关于异或:异或0取本身,异或1取反。注意异或的使用。
(8)C语言中的逻辑运算
(略)
(9)C语言中的位移运算
- 左移 算术右移(补最高位),逻辑右移(补0)
- 移动k位,这里k很大
只考虑 l o g 2 w log_2w log2w位,即位移位数是通过计算 ( k m o d w ) (k\space mod \space w) (k mod w)得到的
如字长为32,移动36位,实际上移动 36 m o d 32 = 4 36 \space mod \space32 = 4 36 mod 32=4位 - 拿不准位运算的优先级时,注意加括号
2.整数表示
(1)整型数据类型
- C语言规定了整型数据的最小表示数据范围,现在的机器往往要比这个数据范围大,
- long 类型在32位和64位数据范围不同,(4/8)
- 正数与负数有不对称性,相差1(显然)
(2)无符号数编码
无符号数编码定义
对
向
量
x
⃗
=
[
x
w
−
1
,
x
w
−
2
,
…
…
x
0
]
;
B
2
U
(
x
⃗
)
=
∑
i
=
0
w
−
1
x
i
2
i
对向量\vec{x} = [x_{w-1},x_{w-2},……x_0];\\ B2U(\vec{x}) = \sum_{i = 0}^{w-1}x_i2^i
对向量x=[xw−1,xw−2,……x0];B2U(x)=i=0∑w−1xi2i
B
2
U
w
B2U_w
B2Uw是一个双射
(3)补码编码
- 最高有效位为符号位,解释为负权
对 向 量 x ⃗ = [ x w − 1 , x w − 2 , … … x 0 ] ; B 2 T ( x ⃗ ) = − x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i 对向量\vec{x} = [x_{w-1},x_{w-2},……x_0];\\ B2T(\vec{x}) = - x_{w-1}2^{w-1}+\sum_{i = 0}^{w-2}x_i2^i 对向量x=[xw−1,xw−2,……x0];B2T(x)=−xw−12w−1+i=0∑w−2xi2i
B 2 T w B2T_w B2Tw是一个双射 - U m a x = 2 × T m a x + 1 U_{max} =2 \times T_{max} + 1 Umax=2×Tmax+1
- C语言没有规定有符号数一定要用补码来表示,但是很多机器都是这样做的,为了保证可移植性,在书写C代码时不应作这样的假设,即有符号数是用补码表示的,但是很多程序都是这样做的,也能在大部分机器上移植。在<limits.h>中定义了INT_MAX,INT_MIN,UINT_MAX的值
(4)有符号数和无符号数的转换
C语言中有符号数和无符号数的强制类型转换并不能是改变了存储方式,只是改变了解释的方式
注意补码的性质,对应有无符号绝对值之和应当是
2
w
2^w
2w
补码转换为无符号数:
T
2
U
w
(
x
)
=
{
x
x
>
0
x
+
2
w
x
≤
0
T2U_w(x) = \begin{cases} x & x>0\\ x + 2^w & x \le 0 \end{cases}
T2Uw(x)={xx+2wx>0x≤0
证明显然