第 2 章 信息的表示和处理

2.1 信息存储

2.1.1 十六进制表示法

2.1.2 字数据大小

C 标准并不保证 char 类型是一个有符号数。如果需要保证其为一个有符号数值,需要显式标明 signed char。不过,在很多情况下,程序行为对数据类型 char 是有符号的还是无符号的并不敏感。

2.1.3 寻址和字节顺序

对于跨越多字节的程序对象,我们必须明确建立两个规则:这个对象的地址是什么,以及在内存中如何排列这些字节。

对于多字节对象,往往都被存储为连续的字节序列,对象的地址就是所使用字节中最小的地址。

而排列一个对象的字节有两个通用的规则。如果选择在内存中按照从最低有效字节到最高有效字节的顺序存储对象,即最低有效字节在最前面的排列方式,称为小端法;如果选择在内存中按照从最高有效字节到最低有效字节的顺序存储对象,即最高有效字节在最前面的排列方式,称为大端法

假设变量 x 的类型为 int,位于地址 0x100 处,它的十六进制值为 0x01234567。地址范围 0x100 ~ 0x103 的字节顺序依赖于机器的类型:

大端法
             0x100     0x101     0x102     0x103
+---------+---------+---------+---------+---------+---------+
|   ...   |    01   |    23   |    45   |    67   |   ...   |
+---------+---------+---------+---------+---------+---------+
小端法
             0x100     0x101     0x102     0x103
+---------+---------+---------+---------+---------+---------+
|   ...   |    67   |    45   |    23   |    01   |   ...   |
+---------+---------+---------+---------+---------+---------+

注意,在字 0x01234567 中,高位字节的十六进制值为 0x01,而低位字节值为 0x67

大多数 Intel 兼容机都只用小端模式。而 IBM 和 Oracle 的大多数机器则是按大端模式操作。实际上,一旦选择了特定操作系统,那么字节顺序也就固定下来。比如许多移动电话的 ARM 微处理器,其硬件可以按小端或大端两种模式操作,但这些芯片上最常见的两种操作系统—— Android 和 iOS ——却只能运行于小端模式。

一般来说,无论哪种字节序对程序员来说都不是直观可见的,但一些场景下字节序却显得格外重要。

一种场景是涉及网络传输时,小端法机器产生的数据被发送到大端法机器或反过来时,接收程序可能会发现收到的字节成了反序的。

另一种场景是阅读机器级程序时,明确哪种序列模式对于阅读整数数据格外重要。比如如下机器级代码表示:

再一种场景是编写规避正常的类型系统的程序。比如 C 语言中,可以通过强制类型转换或联合(union)来允许一种数据类型引用(解析)一个对象,而这种数据类型与创建这个对象时定义的数据类型不同。大多数应用编程都强烈不推荐这种编码技巧,但是它们对系统级编程来说是非常有用,甚至是必需的。

可用如下程序代码来打印程序对象的字节表示:

2.1.4 表示字符串

2.1.5 表示代码

2.1.6 布尔代数简介

2.1.7 C 语言中的位级运算

2.1.8 C 语言中的逻辑运算

2.1.9 C 语言中的移位运算

[!WARNING|style:flat|label:与移位运算有关的操作符优先级问题]

由于移位操作实际效果往往是乘以或除以(整数除)2 的整数次幂,所以程序员往往会以为移位操作的优先级与乘法或除法一致,但实际并非如此。C 语言中,移位运算的优先级要低于加法(和减法)的!所以对于表达式 1 << 2 + 3 << 4 而言,其等价于 1 << (2 + 3) << 4,而非 (1 << 2) + (3 << 4)

此类错误是 C 语言中常见的表达式错误,而且常常很难检查出来。所以对于表达式计算的优先级拿不准的时候,一定要加上括号!

2.2 整数表示

2.2.1 整型数据类型

2.2.2 无符号数的编码

函数 B2Uw(Binary to Unsigned)表示二进制到无符号数编码的映射关系,w 表示长度为 w 位。

对向量

2.2.3 补码编码

函数 B2Tw(Binary to Two's-complement)表示二进制到补码编码的映射关系,w 表示长度为 w 位。

对向量

即将字的最高有效位解释为负权(negative weight)。

[!NOTE|style:flat|label:有符号数的其他表示方法]

有符号数还有两种标准的表示方法:

原码(Sign-Magnitude):最高有效位解释为符号位,其他位正常计算。比如 4 位下,3 表示为 0011,-3 表示为 1011。

原码有以下特点:

  1. 原码表示直观、易懂,与真值转换容易。

  2. 原码中 0 有两种不同的表示形式,给使用带来了不便。

  3. 原码表示加减运算复杂。

反码(Ones' Complement):最高有效位解释为符号位,表示负数时,其他位按原码二进制相应位取反;表示正数时与原码一致。比如 4 位下,3 表示为 0011,-3 表示为 1100。

反码有以下特点:

  1. 在反码表示中,用符号位表示数值的正负,形式与原码表示相同,即 0 为正;1 为负。

  2. 在反码表示中,数值 0 有两种表示方法。

  3. 反码的表示范围与原码的表示范围相同。

反码表示在计算机中往往作为数码变换的中间环节。

对于几种编码,其计算性质上有如下关系:正数的补码等于它的原码;负数的补码等于反码 +1

2.2.4 有符号数和无符号数之间的转换

2.2.5 C 语言中的有符号数与无符号数

当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么 C 语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。这种方法对于标准的算术运算来说并无多大差异,但是对于像 < 和 > 这样的关系运算符来说,它会导致非直观的结果。

2.2.6 扩展一个数字的位表示

2.2.7 截断数字

2.2.8 关于有符号数与无符号数的建议

2.3 整数运算

2.3.1 无符号加法

2.3.2 补码加法

2.3.3 补码的非

2.3.4 无符号乘法

2.3.5 补码乘法

2.3.6 乘以常数

2.3.7 除以 2 的幂

2.3.8 关于整数运算的最后思考

2.4 浮点数

2.4.1 二进制小数

2.4.2 IEEE 浮点表示

2.4.3 数字示例

2.4.4 舍入

2.4.5 浮点运算

2.4.6 C 语言中的浮点数

2.5 小结

Last updated