深入理解计算机系统(二):信息的表示和处理
文章目录
信息是怎样表示的?
大多数计算机使用8位的块,或者字节,作为最小的可寻址的内存单位,而不是访问内存中单独的位。机器级程序将内存看作一个非常大的字节数组,成为虚拟内存,内存的每个字节都由一个唯一的数字来标识,成为它的地址,所有可能地址的集合就称为虚拟地址空间。
十六进制表示法
我们知道一个字节是由8位组成的,在二进制表示法中,范围为00000000 ~ 11111111。转化成十进制,值域就是0~255。这两种方式去描述位,都不太好,因为二进制太长,而十进制还要和二进制相互转换非常麻烦,于是想出了一个折中的方法——使用十六进制表示法。
下图展示了十六进制表示法对应的十进制和二进制。
以0x或者0X开头的数字就是16进制的值。关于十六进制、二进制、十进制之间的转换还是很基础的知识,一定要掌握,这里就不详细介绍啦!
字数据大小是什么
我们在下载某些软件安装包的时候,往往会看到32位和64位的安装包说明。这个32位和64位指的是啥呢?其实就是指32位字长机器和64位字长机器。
每台计算机都有一个字长,指明指针数据的标准大小。虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。比如说我的计算机字长是64位的,那么虚拟地址的范围就是0~2的64次方-1,程序最多访问2的64次方个字节。
大多数的64位计算机可以运行32位机器编译器的程序,但是64位的程序只能在64位机器上运行。
寻址和字节顺序
在几乎所有的机器上,多字节对象被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。比如,类型为int的变量x的地址为0x100,即在C中地址表达式&x的值为0x100,那么x的4个字节将会被存储在内存的0x100、0x101、0x102、0x103位置。
那么具体怎么存放呢?有两种方式,一种是大端法:最高有效字节在最前面,还有一种是小端法:最低有效字节在最前面。还是以刚刚的例子说明,x的16进制值是0x01234567,地址范围0x100~0x103的字节顺序按照大端和小端法如下排列:
采用大端还是小端要看机器的类型,大多数是只用小端模式,也有新的微处理器采用双端法。
对于我们来说,机器使用的字节顺序我们是无法见到的,但是字节顺序对于使用者来说确实很重要的。比如说,一台大端机器和一台小端机器进行网络通信,接收程序中会发现字里的字节是反序的。为了避免这样的问题,接受方就需要做出一些转换。
如何表示字符串和代码
字符串是由某个标准编码来表示,常见的是ASCⅡ字符码。在使用ASCⅡ码作为字符码的任何系统上都会得到相同的结果,和字节顺序、字大小规则无关。文本数据比二进制数据具有更强的平台独立性。
怎么表示代码?在深入理解计算机系统(一)中我们简单介绍过,像hello.c这样的文件是用ASCⅡ字符构成的,属于文本文件,在编译后会生成字节表示的机器代码(二进制代码表示的)。即使是完全相同的进程运行在不同的操作系统上,会有不一样的编码规则。如下面这个sum函数,在不同操作系统上编译的结果存在差异:
int sum(int x,int y){
return x+y;
}
C语言中的常见运算
布尔运算
、&、|和^分别表示对应逻辑运算NOT、AND、OR、EXCLUSIVE-OR,具体的运算规则就不多介绍了。**、&、|形成了一个布尔代数**,布尔代数和整数算术运算有很多相似之处,比如布尔运算& 对|和|对&有分配律,比如a&(b|c)=(a&b)|(a&c),a|(b&c)=(a|b)&(a|c)。
当考虑位向量上的~、&、 ^ 时,会得到一种不同的数学形式,称之为布尔环。有一个非常重要的性质,在刷算法题中可以运用它来解题,(a^ b)^a=b。
位向量还可以用来表示有限集合,又因为布尔运算|和&对应集合的并和交,~对应集合的补,所以用位向量表示集合时,可以非常方便求集合的交并补。
在C语言中是支持按位布尔运算的。
逻辑运算
逻辑运算符||、&&和!,分别对应于命题逻辑中的OR、AND、NOT运算。逻辑运算和按位布尔运算是不同的:
- 按位布尔运算只有在参数被限制为0或1的情况下,才和对应的逻辑运算有相同的行为。
- &&和||与对应的&和|的第二个区别就是,如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。
移位运算
移位运算无非是向左或者向右移动,以向右移动为例,一般有两种形式的右移:逻辑右移和算术右移。x>>k
:
逻辑右移在左端补k个0,算术右移是在左端补k个最高有效位的值,举个例子如下图所示。斜体的数字表示的是左移/右移填充的值。
C没有明确规定对有符号数要有哪种形式的左右移,但是几乎所有的编译器/机器组合都用算术右移。在Java中,x>>k是算术右移,x>>>k是逻辑右移。
试想这样一个问题,一个数据类型是32位,但是我们移动的位数超过32会发生什么样的结果?比如对这个数据类型的变量左移40位。在C中,位移量是通过计算k mod w
得到的,也就是说当w=32,k=40时,实际上会移动40%32=8
位。
如果在表达式中涉及加减法和移位运算要注意咯,加法和减法的优先级比移位运算要高,像1<<2+3<<4得到的结果是512而不是51(1<<2)+(3<<4)
。
如何表示整数
我们描述用位来编码整数时有两种不同的方式,一种只能表示非负数,一种能够表示负数、零和整数。
C支持很多种整型数据类型,其中long的取值范围是唯一一个和机器相关的。在32位机器上long的范围是[-2147483648,2147483647],在64位机器上为[-9223372036854775808,9223372036854775807]。
我们可以看到取值范围是不对称的,负数的范围比整数大1,那么为啥会这样的?我们希望表示负数值是通过补码来实现的,有一半的位模式(符号位设置为1的数)去表示负数,而另一半(符号数设置为0的数)表示非负数,因为0也是非负数,所有这就意味着能表示的整数比负数少一个。
在C中没有要求用补码去表示有符号整数,但是几乎所有的机器是这么做的。