第一部分
程序结构和执行
Content
信号的表示和处理
现代计算机存储和处理的信息以二值信号表示。
2.1 信息存储
最小的可寻址的内存单位是字节(byte)。
2.1.1 十六进制表示法
一个字节由8位组成。
在C语言中,以0x或者0X开头的的数字常量被认为是十六进制的值。
tips:
十六进制转换十进制小技巧:
x = 2 ^n, 当 n 表示成 i + 4 j 的形式,其中0 <= i <= 3,可以把 x 写成十六进制数开头数字为1(i = 0), 2(i = 1), 4(i = 2), 8(i = 3),后面紧跟着 j 个十六进制的 0 。比如:
2048 = 2^11 , n = 11 = i + 4 j = 3 + 4 * 2,后面紧跟着 j 个十六进制的0,从而得到 2048 = 0x800;同理,类似地,512 = 2^9 = 2^(1 + 4 * 2) = 0x200 。
2.1.2 字数据大小
每台计算机都有一个字长(Word size
),指明指针数据的标称大小(normal size
)。
ISO C99
引入了一类数据类型,其数据大小是固定的。比如 int32_t
和int64_t
,它们分别为4个字节和8个字节。
无符号声明,以下4个声明意思等效:
unsigned long
unsigned long int
long unsigned
long unsigned int
程序员应该力图使他们的程序在不同的机器和编译器上可移植。其中一个方面是使程序对不同数据类型的确切大小不敏感。
2.1.3 寻址和字节顺序
#include <stdio.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, size_t len)
{
size_t i;
for(i = 0; i < len; i++)
printf("%.2x", start[i]);
printf("\n");
}
void show_int(int x)
{
show_bytes((byte_pointer)&x, sizeof(int));
}
void show_float(float x)
{
show_bytes((byte_pointer)&x, sizeof(float));
}
void test_show_bytes(int val)
int ival = val;
float fval = (float) ival;
int *pval = &ival;
show_int(ival);
show_float(fval);
show_pointer(pval);
}
int main()
{
test_show_bytes(12345);
return 0;
}
tips:
typedef char *byte_pointer;
等价于
char *start;
2.1.4 表示字符串
C语言中字符串被编码为一个以null
(其值为0)字符结尾的字符数组。
2.1.5 表示代码
考虑如下C函数:
int sum(int x, int y)
{
return x + y;
}
在示例机器上的代码如下:
发现其指令编码是不同的。不同的机器类型使用不同的且不兼容的指令和编码方式。
2.1.6 布尔代数简介
二进制是计算机编码、存储和操作信息的核心,最简单的布尔代数是在二元集合{0, 1}
基础上的定义。
位向量就是固定长度为 w 、由 0 和 1 组成的串。一个很有用的应用就是表示有限集合。
2.1.7 C语言中的位级运算
C语言其中一个特性是它支持按位布尔运算。
以下是以下对char
数据类型表达式求值的例子:
2.1.8 C语言中的逻辑运算
以下为一些表达式求值的示例:
2.1.9 C语言中的移位运算
x << k
, 表示 x
左移 k
位,丢弃最高的 k
位,并在右端补 k
个 0,同理右移类似。 移位分有符号移位和无符号移位。
2.2 整数表示
图2-8为约定的一些数学术语。
2.2.1 整型数据类型
C语言标准定义了每种数据类型必须能够表示的最小的取值范围。如图2-11
2.2.2 无符号数的编码
定义:
示例:
2.2.3 补码编码
图2-14展示了针对不同字长,几个重要数字的位模式和数值。
几个特性:
补码范围不对称性:|TMin| = |TMax| + 1
最大的无符号数值刚好比补码的最大值的两倍大一点:UMax(w) = 2TMax(w) + 1
2.2.4 有符号数和无符号数之间的转换
C语言运行在各种不同的数字类型之间做强制类型转换。强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。
2.2.5 C语言中的有符号数与无符号数
2.2.6 扩展一个数字的位表示
2.2.7 截断数字
2.3 整数运算
2.3.1 无符号加法
模数加法形成了一种数学结构,称为阿贝尔群(Abelian group
)。
2.3.2 补码加法
2.3.3 补码的非
2.3.4 无符号乘法
2.3.5 补码乘法
2.3.6 乘以常数
x * 14 = x *( 2^3 + 2^2 + 2^1) = (x << 3) + (x << 2) + (x << 1) = x * (2^4 - 2^1) = ( x << 4) - (x << 1)
2.3.7 除以2的幂
2.4 浮点数
浮点表示对形如 V = x * 2^y
的有理数进行编码。
2.4.1 二进制小数
十进制表示法使用如下:
二进制表示法如下:
2.4.2 IEEE(I - triple-E)浮点表示
标准浮点格式
浮点格式(C语言中) ` | s |
exp(k) |
frac(n) |
` | |
---|---|---|---|---|---|
单精度(float ) |
1 | 8 | 23 | 32位表示 | |
双精度(double ) |
1 | 11 | 52 | 64位表示 |
根据exp
的值,被编码的值可以分为三种不同的情况
情况1:规格化的值
阶码字段被解释为以偏置(biased
)形式表示的有符号整数。即,E = e - Bias
, 其中 e
是无符号数,而 Bias
是一个等于 2^(k-1) - 1
的偏置值。
尾数定义为 M = 1 + f
。隐含以 1 开头的(implied leading 1
)表示。
情况2:非规格化的值
阶码值是 E = 1 - Bias, 而尾数的值是 M = f ,不包含隐含的开头 1 。
情况3:特殊值
2.4.3 数字示例
位 | e | E | 2^E | f | M | 2^E*M | V | 十进制 |
---|---|---|---|---|---|---|---|---|
0 00 00 | 0 | 0 | 2^0 | 0/4 | 0/4 | 0/4 | 0/4 | 0.00 |
0 00 01 | 0 | 0 | 2^0 | 1/4 | 1/4 | 1/4 | 1/4 | 0.25 |
0 00 10 | 0 | 0 | 2^0 | 2/4 | 2/4 | 2/4 | 1/2 | 0.50 |
0 01 00 | 1 | 0 | 2^0 | 0/4 | 4/4 | 4/4 | 1 | 1.00 |
0 01 01 | 1 | 0 | 1 | 1/4 | 5/4 | 5/4 | 5/4 | 1.25 |
0 01 10 | 1 | 0 | 2^0 | 2/4 | 6/4 | 6/4 | 3/2 | 1.50 |
0 01 11 | 1 | 0 | 2^0 | 3/4 | 7/4 | 7/4 | 7/4 | 1.75 |
0 10 00 | 2 | 1 | 2^1 | 0/4 | 4/4 | 4/2 | 2 | 2.00 |
0 10 01 | 2 | 1 | 2^1 | 1/4 | 5/4 | 5/2 | 5/2 | 2.50 |
0 10 10 | 2 | 1 | 2^1 | 2/4 | 6/4 | 6/2 | 3 | 3.00 |
0 10 11 | 2 | 1 | 2^1 | 3/4 | 7/4 | 7/2 | 7/2 | 3.50 |
0 11 00 | - | - | - | - | - | - | - |
2.4.4 舍入(rounding)
示例
2.4.5浮点运算
2.4.6 C语言中的浮点数
2.5 小结
计算机将信息编码为位(bite
),通常组织成字节序列。有不同的编码方式用来表示整数、实数和字符串。不同的计算机模型在编码数字和多字节数据中的字节顺序时使用不同的约定。
C语言的设计可以包含多种不同字长和数字编码的实现。64位字长的机器逐渐普及,并正在取代统治市场长达30多年的32位机器。由于64位机器也可以运行32为机器编译的程序,我们的重点就放在区分32位和64位程序,而不是机器本身。64位程序的优势是可以突破32位程序具有的4GB地址的限制。
大多数机器对整数使用补码编码,而对浮点数使用IEEE标准754编码。在位级上理解这些编码,并且理解算术运算的数学特性,对于想使编写的程序能在全部数值范围上正确运算的程序员来说,是很重要的。
在相同长度的无符号和有符号整数之间进行强制类型转换时,大多数C语言实现遵循的原则是底层的位模式不变。在补码机器上,对于一个 w 位的值,这种行为是由函数 T2Uw
和 U2Tw
来描述的。C语言隐式的强制类型转换会出现许多程序员无法预计的结果,常常导致程序错误。
由于编码的长度有限,与传统整数和实数运算相比,计算机运算具有非常不同的属性。当超出表示范围时,邮箱长度能够引起数值溢出(overflow
)。当浮点数非常接近于 0.0 , 从而转换成零时,也会下溢。
和大多数其他程序语言一样,C语言实现的有限整数运算和真实的实数运算相比,有一些特殊的属性。例如,由于溢出,表达式 x * x
能够得出负数。但是,无符号和补码的运算都满足整数运算的许多其他属性,包括结合律、交换律和分配律的属性,还利用了移位和乘以2的幂之间的关系。
我们已经看到了几种使用位级运算和算术运算组合的聪明方法。例如,使用补码运算, ~x + 1
等价于-x
。另外一个例子,加速我们想要一个形如[0,……,0, 1,……,1]]
的位模式,有 w - k
个 0 后面紧跟着 k
个 1 组成。这些位模式有助于掩码运算。这种模式能够通过C表达式(1 << k) - 1
生成,利用的是这样一个属性,即我们想要的位模式的数值为2^k - 1
。例如, 表达式(1 << 8) - 1
将产生位模式0xFF
。
浮点表示通过将数字编码为 x * 2^y
的形式来近似地表示实数。最常见的浮点表示方式是由IEEE
标准754
定义的。它提供了几种不同的精度,最常见的是单精度(32位)和双精度(64位)。IEEE浮点也能够表示特殊值 +无穷
、-无穷
和NaN
。
必须非常小心地使用浮点运算,因为浮点运算只有有限的范围和精度,而且并不遵守普遍的算术属性,比如结合性。