第2章 信息的表示和处理

指针与字长

每台计算机都有一个字长,用来指明指针数据的标称大小(nominal size),因为虚拟地址是以这样的一个字来编码的。
字长决定计算机虚拟地址空间的最大大小,字长为 ,那么虚拟地址范围为
64位机器一般也可以运行32位程序。在 gcc 中加入 -m32 表示指定生成32位程序,-m64则是64位。

long 在大部分32位机器中占4个字节,而在64位机器中占8个字节
char在大部分机器中是有符号的,但是 C 标准不保证这一点。
使用数据类型 int32_t int64_t来保证数据大小固定不随编译器和机器变化

寻址与字节顺序

最低有效字节在最前面为小端法,最高有效字节在最前面为大端法。
现代通用处理器同时支持小端法和大端法,具体选择根据操作系统决定。
在网络协议中,发送方和接收方都需要把数据转化为网络协议格式以避免字节顺序不一致带来的问题。
文本信息在任何字节顺序的系统上均得到相同的结果,具有更强的平台独立性。

位移运算

左移运算x << k将x左移k位,丢弃最高的k位,并在右边补k个0。
右移运算分为两种,逻辑右移和算术右移,两者都是右移,区别是在左端补充的内容,前者是在左端补0,后者是在左边补最高有效位的值。这样设计对有符号整数数据运算有特别意义。
注意C标准并未对有符号整数应该使用哪种类型的右移作规定,但在几乎所有的机器上都对有符号数算术右移。
Java中对右移有明确规定,x>>k表示算数右移,x>>>k表示逻辑右移。

补码编码

C标准并没有要求要用补码形式来表示有符号数,但是几乎所有的机器都依照补码标准。

确定大小的整数

使用intN_t,uintN_t这样的声明来定义确定大小的整数类型,如int32_t, uint64_t
在使用printf相关格式打印时,对应的格式串需要扩展宏来保证不论代码在任何机器上编译都能得到正确的效果。如

int32_t x;
uint64_t y;
printf("x = %" PRId32 ",y = %" PRIu64 " "\n", x, y);

C预处理器在遇到仅有空格或其它空白字符分割的字符串时,就会把它们串联起来。

有符号数和无符号数

C标准并没有规定有无符号的数之间的转化,但一般机器遵循的原则均是按照补码保持底层的位不变。
在执行运算的时候,若运算数中既有有符号数又有无符号数,C语言会隐式地将有符号数强制转化为无符号数,并假设两者都是非负的。这在<,>这类比较运算符中可能导致非直观的效果。

数字位的扩展和截断

无符号数在扩展位补0,补码数在扩展位补充原最高有效位。这样定义可以保证补码扩展后值保持不变。
对于同时改变是否有符号位和数字位的变化,C标准规定,先改变大小,再进行有符号到无符号的转化。比如从shortunsigned,先进行(int)再进行unsigned
不论是否为补码表示,截断则是直接对底层的位进行截断。

加法溢出

无符号数:相当于模意义下加法。
有符号数补码:溢出截断,等价于看作无符号数进行加法。

乘法溢出

无论无符号数还是有符号数补码的乘法都是对结果进行截断。
对于二进制位表示相同的无符号和补码乘法,虽然完整的乘积的位数表示可能不同,但截断之后的结果是相同的。

整数除法

定义整数的除法为舍入到零。
对于除以 2 的幂的情况,当对无符号使用位移运算是很方便的,直接右移即可。
对于有符号数,正数的情况下也可以直接右移,但是对于负数的情况,需要加上一个偏置 bias 来修正以达到向零舍入,否则直接右移运算是向下舍入。具体而言,对于负数 ,用 来计算。

IEEE浮点表示

IEEE浮点标准用 来表示一个数。其中 为符号位,决定是负数还是正数,对于0则有特殊解释。表示尾数,是一个二进制小数。 是阶码,对浮点数进行加权。在具体的浮点数位表示中,通常是一个单独的符号位编码,长度为的阶码字段编码阶码,长度为 的小数字段编码
根据 的值,可以将编码的值分为三个部分:
即不是全为 0 也不是全为 1 时。此时表示的是规格化的值。阶码的值被解释为以偏置形式表示的有符号整数,即 ,这里 。小数字段被解释为 为 0 到 1 的小数,而位数定义为 。通过这种技巧可以额外获得一个精度位,因为我们总能通过调整阶码使得尾数在 1 和 2 之间。
全为 0 时。此时表示的时非规格化的值。此时阶码值为 ,尾数值 ,注意到与规格化不同的是,这里的尾数是不包含隐含的开头 1 的。阶码这样设置实现了从非规格化值到规格化值的平滑转化。且非规格化表示能够使数值比较均匀地分布在 附近。注意,修改符号位可以得到 的位表示不同,根据 IEEE 标准,两者在某些方面被认为是不同的,而在其他方面是相同的。
全为 1 时。此时表示特殊的值。小数部分全 0 时,表示无穷,并根据符号位区分正无穷和负无穷。小数部分非零时则表示 ,即不是一个数。
IEEE 浮点表示的一个优势是,若将其位解释为无符号数时大小关系依然不变,当然对负数的符号位需要特殊处理一下,但仍能避开浮点数运算就可以进行比较和排序。

IEEE浮点舍入和运算

IEEE 浮点规定向偶数舍入。
IEEE 浮点运算定义为算术运算后进行舍入的结果。这意味着浮点加法不再具有交换性,但仍保持单调性。浮点乘法具有交换性,但不具有结合性,对加法不具有分配性

C语言中的浮点数

在 C 语言中提供了 floatdouble 两种不同的浮点类型,分别对应单精度和双精度浮点。在支持 IEEE 标准格式的机器上,还遵循向偶数的舍入方式。但需要注意的是,C 语言标准并未要求机器使用 IEEE 浮点。
在 GNU 编译器 GCC 中,若出现下列语句,编译器会定义常数 INFINITYNAN 分别用来表示正无穷和

#define _GNU_SOURCE 1
#include <math.h>

C 语言中浮点与整型的转换

int 转换到 float,不会发生溢出,但可能发生舍入。
intfloat 转换到 double,由于 double 的范围更大,精度也更高,故不会发生溢出或舍入。
double转换到float,可能发生溢出,同时可能发生舍入。
floatdouble转换到int,会向零舍入,同时值可能会发生溢出。C 语言标准并未对溢出的情况指定固定的结果,但与 Intel 兼容的微处理器会指定为位模式[10..0]整数不确定值,这个值会在浮点数找不到合理的整数近似值时产生。