一、前言
现代计算机存储和处理的信息以二值信号表示,即由数学上的数字 0 和 1 并以二进制的形式表示数字信息,在计算机专业中对其有个专业的术语:位(bit)。
十进制是逢十进一,二进制则是逢二进一,八进制是逢八进一,十六进制是逢十六进一。
为什么是二进制成了计算机信息存储的基础机制?因为二值信号能够很容易地被表示、存储和传输,例如,可以表示为穿孔卡片上有洞或无洞、导线上的高电压或低电压,或者顺时针或逆时针的磁场。另外从技术支持的成本上,硬件处理二进制的能力比其他进制简单可靠。
单个的位没有太多的实际作用,但是把 n 多的位进行组合排列就会产生任何有限集合的元素信息。
上图中多个位进行组合的可能值信息赋予一定的解释就能进行数学上的数值表示,其中有三种最重要的数字表示:无符号(unsigned)、补码(two’s-complement)、浮点数(floating-point)。
无符号(unsigned)编码基于传统的二进制表示法,表示
大于或者等于零的数字。补码(two’s-complement)编码是表示有符号整数的最常见的方式,有
符号整数就是可以为正或者为负的数字。浮点数(floating-point)编码是表示实数的科学记数法
的以二为基数的版本。
注意的是,计算机的表示法是用有限数量的位来对一个数字编码,当结果太大以至不能表示时(有限的位装不下要表示的数字时),某些运算就会溢出(overflow)。
对于不同的计算机来说,由于数值范围可能有所不同,因此掌握信息相关的内容对于写出跨平台的程序来讲也是很有帮助的。
二、信息存储
组合的位能够表示更多的信息,因此计算机的信息就存储在这些位中,大多数计算机使用 8 位的块,或者字节(byte),作为最小的可寻址的存储器单位,而不是在存储器中访问单独的位。
机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器(virtual memory)。存储器的每个字节都由一个唯一的数字来标识,称为它的地址(address),所有可能地址的集合称为虚拟地址空间(virtual address space)。
程序跑在操作系统之上,就只需要利用好自己的虚拟地址空间(操作系统分配的),而无需关注硬件是怎么实现位信息的存储,因此有强大庞杂的操作系统完成了与硬件的实现。
具体来说,就是编译器和运行时系统将存储器空间划分为更可管理的单元,以存放不同的程序对象(program object),即程序数据、指令和控制信息。并可以用各种机制来分配和管理程序不同部分的存储,这种管理完全是在虚拟地址空间里完成的。
十六进制
十进制是使用 0 - 9 的自然数字进行表示,十六进制的则是使用 0 - 15 的符号来表示,但是有一个问题在于,一个位只能存储一个符号信息,例如 10 这样的十进制数字需要两位来存储表示,因此需要使用一些符号替代两位数的数字,科学家们规定 10 - 15 对应英文字母的 A - F。在 C 语言中一般是使用“0x”(数字 0 和字母 x )开头表示的数字为十六进制数。
为什么要使用十六进制呢,因为计算机在设计之初就是以 8 位为信息存储单元,使用十六进制可以达到更小的存储单位中表达更多的信息。下图中每个十六进制数字都对 16 个值中的一个进行了编码。
字
每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以操作系统的字长决定了虚拟地址空间的最大大小。
三、数据类型和大小
讲述了二进制可以表示整数,如果需要计算机操作小数操作或者计算出的结果是小数,需要如何表示是一个需要值得研究的,同时,计算机中存储的都是 0 和 1 的位信息,计算机是怎么知道从哪里开始到哪里结束表示一个数字呢,这就需要引出数据类型和对应的所占位数大小。
四、寻址和字节顺序
上述已经得知,一个程序运行起来之后,就会由操作系统分配虚拟地址空间供自己自由使用,那么程序总需要一个地方来维护所有信息,并且能够按照与预期效果一致的顺序读取字节信息。
对于跨越多个字节的程序对象(程序对象指指令、数据或者控制信息等,是程序当中对象的统称)来说,我们需要制定两个规则才能唯一确定一个程序对象的值:对象的地址和对象表示的字节顺序。
对象地址
在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。假设一个类型为 int 的变量 a 的地址为 0x100,也就是说,地址表达式 &a 的值为 0x100。那么,a 的 4 个字节将被存储在存储器的 0x100、0x101、0x102 和 0x103 位置。
字节顺序
某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,即最低有效字节在最前面的方式,称为小端法(little endian)。
而另一些机器则按照从最高有效字节到最低有效字节的顺序存储,最高有效字节在最前面的方式,称为大端法(big endian)。
有些机器或操作系统支持大端法,有些支持小端法,还有些处理器能够支持双端表示,那么如何在不同计算机之间进行信息传递,尤其是在网络传输数据的过程中,必须遵循全球通用的字节顺序标准,才能使得信息互联,但是大多数情况下,程序员不需要考虑大小端问题,因为编译器已经帮我们做了字节顺序自适应。
参数 12345 的十六进制表示为 0x00003039。我们可以看到在 Linux 32、Windows 和 Linux 64 上,最低有效字节值 0x39 最先输出,这说明它们是小端法机器;而在 Sun 上却最后输出,这说明 Sun 是大端法机器。
五、字符串和代码表示
计算机如何通过位信息来表示字符串也是值得研究,在 C 语言中,字符串编码是以 NULL 字符结尾的字符数组,因此其字符串在计算机中表示的本质就是以 0 结尾的字符数组。如何将数字编码成对应的数字有一定的国际标准,最常见的就是 ASCII 码编码规则。
以字符串"123456"
为例,通过 C 语言转码得到:
1 | 31 32 33 34 35 36 00 |
可以发现,字符串中包含的数字 x 在经过 C 语言程序转换,得到的十六进制数为:0x3x,并且最终结尾是:0x00。
在使用 ASCII 码作为字符码的任何系统上都将得到相同的结果,与字节顺序和字大小规则无关。
下面代码在不同的操作系统上编码方式不一样:
1 | int sum(int x, int y) { |
在不同机器上编译后,得到的机器代码不一样:
1 | Linux 32: 55 89 e5 8b 45 0c 03 45 08 c9 c3 |
因此,源代码一样的文件在不同操作系统上编译之后,生成的二进制代码是不兼容的。
六、小结
计算机与硬件交互是用过二进制信息完成交互,为了方便管理、提升系统性能,操作系统对于数据操作单元的位数选取不同,由于 32 位操作系统对于虚拟地址空间的限制,导致目前的计算机逐渐迁移到了 64 位。
另外,不同的操作系统对于字节的顺序表示和数据类型占用位大小也不同,有大端表示的,也有小端表示的。这种单机的独特性导致计算机在网络中传输的字节顺序不一致,因此信息数据在网络交互过程中需要遵循一个唯一的标准。
所以计算机都遵循 ASCII 码编码标准,使得字符串和代码(字符串编写的文本文件)的编码和解码操作变得可以不依赖平台进行移植。当然代码中定义的数字信息还是需要依赖编译器进行编译成适合本系统的机器代码(二进制文件)。