2.1 信息存储
1、概述
1. 几个基本概念
-
最小的可寻址的内存单位为一个8位的块,也就是一个字节。
-
机器级程序将内存视为一个非常大的字节数组, 称为虚拟内存 (virtual memory)。
-
内存的每个字节 都由一个唯一的数字来标识, 称为它的地址( address),
-
所有可能地址的集合就称为虚拟地址空间(virtual address space)
顾名思义, 这 个虚拟地址空间只是一个展现给机器级程序的概念性映像。 实际的实现(见第9章)是将动态随机访问存储器(DRAM)、闪存、 磁盘存储器、 特殊硬件和操作系统软件结合起来, 为 程序提供一个看上去统一的字节数组。
2、十六进制表示法
1. 基本概念
十六进制(简写为 “hex")使用数字 ‘0’~'9’以及字符 ‘A’~ 'F’来表示16个可能的值。用十六进制书写,一个字节的值域为
0
0
16
00_{16}
0016 ~
F
F
16
FF_{16}
FF16。也就是一个字节可以由两位16进制数字表示。
2.进制转换
根据定义即可完成转换
-
十六进制转换成二进制
可以通过展开每个十六进制数字,将它转换为二进制格式,例如0x173A4C
就可以得到结果 -
二进制转十六进制
可以通过首先把它分为每4位一组来转换为十六进制。不过要注意,如果位总数不是4的倍数,最左边的一组可以少于4位,前面用0补足。然后将每个4位组转换为相应的十六进制数字。
-
十进制转换成十六进制
将一个十进制数字 x 转换为十六进制 ,可以反复地用16除x, 得到一个商q和一个余数r, 也就是 x=q • 16+r。 然后, 我们用十六进制数字表示的r作为最低位数字, 并且通过对q反复进行这个过程得到剩下的数字。 例如, 考虑十进制 314156的转换:
我们能读出十六进制表示为0x4CB2C -
十六进制转换为十进制
我们可以用相应的16的幕乘以每
个十六进制数字。比如, 给定数字Ox7AF, 我们计算它对应的十进制值为7•162 +10-
16+15 = 7 • 256+10 • 16+15 = 1792+160+15 = 1967。
3、字数据大小
1.字长字节数
用来指明指针数据的标称大小,也就是一个指针占用的字节数。因为 虚拟地址是以这样的一个字来编码的, 所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为w位的机器而言,其虚拟地址的范围为
0
2
w
−
1
0~2^w - 1
0 2w−1,程序最多访问
2
w
2^w
2w个字节。
我们常说的32位机器和64位机器指的就是字长。
我们将程序称为“ 32位程序 ” 或 “64位程序”时, 区别在于该程序是如何编译的,而不是其运行的机器类型。64位的机器也可以运行为32位机器编译的程序 。但是后者不能运行为前者编译的程序。
2.各种数据类型的字节数
上图是基本C数据类型的典型大小。分配的字节数受程序是如何编译的影响而变化。
本图给出的是32位和64位程序的典型值。
ISO C99 引入了 一类数据类型, 其数据大小是固定的, 不随编译器和机器设置而变化。 其中就有数据类型 int32_t 和 int64_t, 它们分别为 4 个字节和 8 个字节。
图中可以看到char*代表的指针使用的是程序的全字长,long在不同位的机器上所占的字长也不一样。
程序员应该切实的提高程序对于不同机器和编译器的可移植性。其一方面就是让程序对于不同数据类型的确切大小不敏感。比如, 假设一个声明为 int类型的程序对象能被用来存储一个指针。 这在大多数 32 位的机器上能正常工作, 但是在一台 64 位的机器上却会导致问题。
4、寻址和字节顺序
对于跨越多个字节的程序对象来说,必须明确两个问题
- 用哪个字节的地址来表示整个对象的地址
- 内存中如何排列这些字节
1.对象的地址
在几乎所有的机器上, 多字节对象都被存储为连续的字节序列, 对象的地址为所使用字节中最小的地址。
例如 一个int型变量,他的地址是0x100,他的四个字节依次被存储在0x100、0x101、0x102、0x103这几个位置。
2.排列顺序
对于一个数字x,假设其位表示为 [ x w − 1 , x w − 2 , . . . , x 1 , x 0 ] [x_{w-1},x_{w-2},...,x_1,x_0] [xw−1,xw−2,...,x1,x0],其中 x w − 1 x_{w-1} xw−1是最高有效位, x 0 x_0 x0为最低有效位。如果w是八的倍数,那么就可以被分成若干个字节,这些字节的排序有两种情况
-
小端法
就是最低有效字节在最前面 -
大端法
就是最低有效字节在最后面
例如假设变量x 的类型为int, 位于地址0x100处, 它的十六进制值为0x01234567。地
址范围0x100~ 0x103 的字节顺序依赖于机器的类型:
选择好确定的操作系统之后,字节顺序就固定下来了。
在C 语言中,我们能够用数组表示法来引用指针,同时我们也能用指针表示法 来引用数组元素。在这个例子 中·,引用start[i]表示我们想要读取以start 指向的位置为起始的第i个位置处的字节。
5、表示字符串
c语言中,字符串被编码为一个以null结尾的字符数组。每个字符都由某个标准编码来表示, 最常见的是ASCII 字符码。 在使用 ASCII 码作为字符码的任何系统上都将得到相同的结果, 与字节顺序和字大小规则无关。 因而, 文本数据比二进制数据具有更强的平台独立性。
UTF-8
UTF-8编码,是变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1识别。这样对以7位ASCII字符为主的西文文档就大幅节省了编码长度
Java编程语言使用Unicode来表示字符串。 对于C语言也有支持Unicode的程序库。
6、表示代码
不同的机器类型使用不同的且不兼容的指令和编码方式。 即使是完全一样的进程, 运行在不同的操作系统上也会有不同的编码规则, 因此二进制代码是不兼容的。 二进制代码很少能在不同机器和操作系统组合之间移植。
7、布尔代数简介
最简单的布尔代数是在二元集合{0,1}的基础上结合几种运算符号定义的。
可以将上面四个布尔运算扩展到向量中去。位向量是一种固定长度w,由0和1组成的向量。
位向量一个很有用的应用就是表示有限集合。
位向量a = [01101001]表示集合A={0, 3, 5, 6},而b=[01010101]表示集合B ={0, 2, 4, 6}。使用这种编码集合的方法,布尔运算I和&分别对应于集合的并和交,而~对应于于集合的补。还是用前面那个例子,运算a&b得到位向量[01000001],而A交B = {0, 6}。
8、c语言中的移位运算
1.左移
<< 运算符将会将x向左边移动k位,丢弃最高的k位,并在最右边补齐k个0
2.右移
>> 运算符将会将x向右边移动k位,丢弃最低的k位,但是补齐有两种方式
- 逻辑右移
在左端补k个0 - 算数右移
在左端补k个最高有效位的值
我们看下面的例子
实际上,几乎所有的编译器/机器组合都对有符号数使用算术右移。
C语言标准
没有明确定义对于有符号数应该使用哪种类型的右移
对于一个w位的数据,移动k>=w位的时候,没有定义应该如何计算。
实际上许多机器上 位移量就是通过计算k mod w得到的
Java对于如何进行右移有明确的定义。表达是x>>k会将x算术右移k个位置,而x>>>k会对x做逻辑右移。
Java特别要求位移数量应该按照我们前面所讲的求模的方法来计算。