这一章读起来让人多少有点崩溃,然后想到了作者在开篇所说:如果你全力投身学习书本中的概念,完全理解底层计算机系统以及它对应用程序的影响,那么你会步上成为为数不多的“大牛”的道路。
2.1 信息存储
- 大多数计算机使用8位的块,或者字节(byte),作为最小的可寻址的内存单位,而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组,称为虚拟内存(virtual memory)。内存的每个字节都由一个唯一的数字来标识,称为它的地址,所有可能地址的集合称为虚拟地址空间(virtual address space)。
- 一个字节由8位组成,在二进制表示法中,值域位:00000000 ~ 11111111。在十进制表示法中值域为:0 ~ 255。在十六进制表示法中值域为00 ~ FF。
- 每台计算机都有一个字长(word size),虚拟地址是以这样一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。对于一个字长为w位的机器而言,虚拟地址的范围为$ 0 $ ~ $ 2^w-1 $,程序最多访问$ 2^w $个字节。
- 大多数64位机器也可以运行32位机器编译的程序,这是一种向后兼容。我们将程序称为“32位程序”或“64位程序”时,区别在于该程序是如何编译的,而不是其运行的机器类型。
- 程序员应该力图使他们的程序在不同的机器和编译器上可移植。可移植的一个方面就是使程序对不同数据类型的确切大小不敏感。
- 排列表示一个对象的字节有两个通用的规则。
- 小端法:按照从最低有效字节到最高有效字节的顺序在内存里存储对象。
- 大端法:按照从最高有效字节到最低有效字节的顺序在内存里存储对象。
- 大多数Intel兼容机都只用小端模式;IBM和Oracle的大多数机器则按大端模式操作。许多比较新的微处理器是双端法,但是一旦选择了特定操作系统,那么字节顺序也就固定下来了。比如用于移动电话的ARM微处理器,硬件支持双端操作,但是这些芯片上最常见的两种操作系统Android和IOS却只能运行小端模式。
- 不同的机器类型使用不同的且不兼容的指令和编码方式。即使是一样的进程,运行在不同的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的。二进制代码很少能在不同的机器和操作系统组合之间移植。
- 布尔运算符 ~、&、|、^ 分别对应逻辑运算符 NOT、AND、OR、EXCLUSIVE-OR。
- C语言支持按位布尔运算,所使用的符号就是布尔运算符;C语言还提供了一组逻辑运算符||、&&、和!,分别对应于命题逻辑里的OR、AND、和NOT运算。
- 逻辑运算很容易和位级运算相混淆,但是其功能是完全不同的:
- 逻辑运算认为所有非零的参数都表示TRUE,而参数0表示FALSE。
- 如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。
- C语言中的移位:
- 左移:x<<k,x向左移动k位,丢弃最高k位,并在右端补k个0。
- 右移:x>>k,逻辑右移,在左端补k个0;算术右移,在左端补k个最高有效位的值。(填上符号位)
- 事实上,几乎所有的编译器/机器组合都对有符号数使用右移;对无符号数,右移必须是逻辑的。
2.2 整数表示
- 无符号数的编码:
在这个等式中,函数$B2U_w$将一个长度为w的0、1串映射到非负整数。 - 补码(Two’s complement)编码:
最高有效位$x_{w-1}$也称为符号位,它的权重为$-2^{w-1}$,是无符号表示中权重的负数。符号位被设置为1时,表示值为负;为0时,值为非负。 - 反码(Ones’complement):除了最高有效位的权是$-(2^{w-1}-1)$,而不是$-2^{w-1}$,它和补码是一样的:
- 原码:最高有效位是符号位,用来确定剩下的位应该取正权还是负权:
- 原码和反码都有一个奇怪的属性,对数字0有两种不同的编码方式。正0都表示为[00…0],负0在原码中表示为[10…0],在反码中表示为[11…1]。
2.3 整数运算
- 无符号数加法:
- 补码加法:
- 无符号乘法:
- 补码乘法:
2.4 浮点数
- 定点数:
用23位表示整数,这范围比8位大多了, 但是精度又会受到损失了,可见用这种定点数的表示法,范围和精度是一对儿矛盾。如果再定义fixed number C, fixed number D, 程序员简直就不知道用哪个了,并且实现他们之间的计算也很麻烦。所以定点数并不是完美的解决方案。 - 浮点数:
浮点数就是利用指数达到了小数点“浮动”的效果。从而可以灵活地表达更大范围内的