2.5 小结
2.5 小结
计算机将信息编码为位(比特),通常组织成字节序列。有不同的编码方式用来表示整数、实数和字符串。不同的计算机模型在编码数字和多字节数据中的字节顺序时使用不同的约定。
C 语言的设计可以包容多种不同字长和数字编码的实现。64 位字长的机器逐渐普及,并正在取代统治市场长达 30 多年的 32 位机器。由于 64 位机器也可以运行为 32 位机器编译的程序,我们的重点就放在区分 32 位和 64 位程序,而不是机器本身。64 位程序的优势是可以突破 32 位程序具有的 4GB 地址限制。
大多数机器对整数使用补码编码,而对浮点数使用 IEEE 标准 754 编码。在位级上理解这些编码,并且理解算术运算的数学特性,对于想使编写的程序能在全部数值范围上正确运算的程序员来说,是很重要的。
在相同长度的无符号和有符号整数之间进行强制类型转换时,大多数 C 语言实现遵循的原则是底层的位模式不变。在补码机器上,对于一个 w 位的值,这种行为是由函数 T2Uw 和 U2Tw 来描述的。C 语言隐式的强制类型转换会出现许多程序员无法预计的结果,常常导致程序错误。
由于编码的长度有限,与传统整数和实数运算相比,计算机运算具有非常不同的属性。当超出表示范围时,有限长度能够引起数值溢出。当浮点数非常接近于 0.0,从而转换成零时,也会下溢。
和大多数其他程序语言一样,C 语言实现的有限整数运算和真实的整数运算相比,有一些特殊的属性。例如,由于溢出,表达式 x*x 能够得出负数。但是,无符号数和补码的运算都满足整数运算的许多其他属性,包括结合律、交换律和分配律。这就允许编译器做很多的优化。例如,用 (x<<3)-x 取代表达式 7*x 时,我们就利用了结合律、交换律和分配律的属性,还利用了移位和乘以 2 的幂之间的关系。
我们已经看到了几种使用位级运算和算术运算组合的聪明方法。例如,使用补码运算,~x+1 等价于 -x。另外一个例子,假设我们想要一个形如 [0, ..., 0, 1, ..., 1] 的位模式,由 w-k 个 0 后面紧跟着 k 个 1 组成。这些位模式有助于掩码运算。这种模式能够通过 C 表达式 (1<<k)-1 生成,利用的是这样一个属性,即我们想要的位模式的数值为 2k-1。例如,表达式 (1<<8)-1 将产生位模式 0xFF。
浮点表示通过将数字编码为 x × 2y 的形式来近似地表示实数。最常见的浮点表示方式是由 IEEE 标准 754 定义的。它提供了几种不同的精度,最常见的是单精度(32 位)和双精度(64 位)。IEEE 浮点也能够表示特殊值 +∞、-∞ 和 NaN。
必须非常小心地使用浮点运算,因为浮点运算只有有限的范围和精度,而且并不遵守普遍的算术属性,比如结合性。
参考文献说明
关于 C 语言的参考书 [45, 61] 讨论了不同的数据类型和运算的属性。(这两本书中,只有 Steele 和 Harbison 的书 [45] 涵盖了 ISO C99 中的新特性。目前还没有看到任何涉及 ISO C11 新特性的书籍。)对于精确的字长或者数字编码,C 语言标准没有详细的定义。这些细节是故意省去的,这样可以在更大范围的不同机器上实现 C 语言。已经有几本书 [59, 74] 给了 C 语言程序员一些建议,警告他们关于溢出、隐式强制类型转换到无符号数,以及其他一些已经在这一章中谈及的陷阱。这些书还提供了对变量命名、编码风格和代码测试的有益建议。Seacord 的书 [97] 是关于 C 和 C++ 程序中的安全问题的,本书结合了 C 程序的有关信息,介绍了如何编译和执行程序,以及漏洞是如何造成的。关于 Java 的书(我们推荐 Java 语言的创始人 James Gosling 参与编写的一本书 [5])描述了 Java 支持的数据格式和算术运算。
关于逻辑设计的书 [58, 116] 都有关于编码和算术运算的章节,描述了实现算术电路的不同方式。Overton 的关于 IEEE 浮点数的书 [82],从数字应用程序员的角度,详细描述了格式和属性。