在空白文档里输入一个大写字母 X,它立刻就出现在屏幕上。你认得这个字母,它也许还是某个单词的开头。但键盘并没有把一幅微型的 X 图画送进计算机。从手指按下按键到屏幕显示字母,其间的电活动变成了信息,信息经过解释,计算机再按照指令画出了你能认出的形状。
在上一篇文章《计算机如何工作:费曼的高速档案员》中,我们借用了理查德·费曼的模型:一名档案员按照指令,从编号的抽屉里读取数值。这个模型说明,计算机怎样通过一连串简单操作完成复杂工作。现在,我们来仔细看看抽屉里究竟装着什么,电子机器又怎样利用这些内容做事。答案从两个符号开始:0 和 1。
一个开关可以保存一位信息
想象一个电灯开关。在这里,我们只考虑它的两种状态:关和开。我们可以用任何符号标记它们,但用 0 和 1 最方便。在两种可能性中作出一次选择,就是一个比特,也叫二进制位。一个比特能区分两种状态,却无法单独告诉我们一个人的名字,也无法描述一张照片。
计算机内部有许多微小的电子元件,它们起着开关的作用。把大量只有两种状态的开关组合起来,计算机就能表示远不止开和关的信息。
四个比特就能数数
想象把四个开关并排放好。从左到右,分别赋予它们 8、4、2、1 这四个值。开关打开,就把对应的值计入;开关关闭,就不计入。把它们设成 0 1 0 1:关、开、关、开。第二个开关的值是 4,第四个是 1,加起来就是 4 + 1 = 5。
换一种组合试试。0111 表示 7,1111 表示 15。这就是二进制:只用 0 和 1 来写数字。它与我们熟悉的十进制遵循相似的原理,只不过越往左,每一位的位值翻一倍,而不是变成十倍。
每个比特只有 0 和 1 两种状态,所以四个比特共有 2 × 2 × 2 × 2 = 16 种组合。八个比特通常组成一个字节,可以形成 256 种组合。增加比特数量,就能表示更大的数字,而电子元件不需要识别更多种状态。数数只是这些模式的第一个用处。
一个数字也能代表一个字母
回到屏幕上的 X。如果要存储一个字母,就需要一条规则,把某个数字与这个字母联系起来。早期文本标准 ASCII 提供了一个简单例子:它把数字 88 分配给大写字母 X。
用二进制可以把 88 写成 01011000。最前面的 0 只是补位,让原本七位的 ASCII 编码在显示时占满八位。其中的 1 分别位于权值为 64、16 和 8 的位置,相加正好是 88。如果程序把这些比特当作数字读取,得到的就是八十八;如果按 ASCII 规则解释,它们代表的就是字符 X。
同样的比特,按照 ASCII 规则解释。
这个模式的形状与 X 毫无相似之处。两者之间的联系来自人们约定的解释规则。
现代计算机需要表示的字符远比 ASCII 能覆盖的多,包括世界各地的文字和表情符号。因此,它们使用范围更广的标准,尤其是 Unicode,以及把字符编码成字节的不同方法。具体做法可以不同,但原理不变:存储的数值依照约定的规则才有意义。单词就是按这种方式表示的一串字符;程序可以依次存储和读取 B、O、X 的编码,而不需要为 BOX 这个词配备专门的硬件。
颜色也遵循相同的原理。描述屏幕像素的一种常见方法,是用三个数字表示红、绿、蓝三种光的强度。在每个颜色通道用八位表示的常见示例中,(255, 0, 0) 就是红色:红色分量达到最大,绿色和蓝色分量为零。换一套约定,同一组数字就会有不同的含义。因此,文字和图像可以利用同样的基本电子机制来存储,却不属于同一种信息。
现在,我们大致知道费曼的档案员可能在抽屉里找到什么了:一个数值,而当前任务规定了该怎样理解它。但存储一个数值,并不等于用它做事。接下来还需要指令。
电子电路怎样完成工作
内存保存着运行中的程序所需的信息,就像费曼模型里的抽屉。中央处理器(CPU)执行指令,进行算术运算和比较,并帮助决定下一步做什么。在现代计算机中,这些工作分布在许多电子电路上,但“读取一个值、对它进行操作、记录结果”仍是理解其工作方式的好办法。
计算机可以用二进制做加法。例如,0101 表示 5,0001 表示 1。把它们相加,得到 0110,也就是 6。
指令本身也由 0 和 1 表示。有些比特告诉处理器要做什么,另一些则指出要使用哪些值,或到哪里去取,例如存放在寄存器中(处理器内部的一小块存储空间)。这些操作有简短的名称:MOVE 用来复制一个值,ADD 用来相加,COMPARE 用来比较,JUMP 让处理器跳转到另一条指令。CALL 开始执行另一组指令,RETURN 随后让处理器返回。处理器按指令执行,必要时重复步骤或改变执行路线,便运行起整个程序。
软件与硬件正是在这里相遇:指令让电子电路有了要完成的工作。
再想想费曼的档案员。他按照指令找到编号正确的抽屉,处理里面的内容,有时还会跳到另一道步骤,而不是接着做下一步。处理器遵循同样的基本思路,通过一连串简单操作执行程序。
这个例子省略了大量工程细节,例如处理器怎样组织内部工作空间,又怎样协调许多操作。但理解核心关系并不需要掌握这些细节。计算机存储信息的表示形式,按照指令处理它们,再把新的表示形式记录为结果。
字母 X 怎样出现在屏幕上
现在可以从头追踪这个字母的出现过程了。按下一个键时,键盘硬件会向设备报告一次输入。操作系统和应用程序根据键盘布局、修饰键等信息来解释输入,判断你想输入哪个字符,并在文档中存储这个字符的表示形式。
要显示文字时,应用程序只知道字符的数字编码还不够,还需要知道它应该是什么样子。这正是字体的作用之一。文字渲染软件利用选定的字体计算字符的形状和位置,再决定屏幕上哪些像素应该显示什么颜色。最后,屏幕发出的光组成了你看到的 X。
换一种字体,X 的外观可能大不相同。字符本身没有变,变化的是绘制它所用的信息和指令。同样,数字 88 也不会因为对应的比特被存到了某个地方,就自动在屏幕上变成 X。程序必须选择按文本解释这些比特,并安排把文本显示出来。
这样就能分清几个容易混淆的角色。电路的不同状态让计算机能够可靠地存储和处理模式。编码规则告诉程序一个模式代表什么。指令决定怎样使用它。最后,屏幕把结果变成可见的东西。文档中的字母是整套过程共同作用的结果。
图像和声音也是同一个道理
拍摄照片时,输入从光开始,而不是从按键开始。相机传感器测量照射到许多微小区域的光,设备再把测量结果转换为数字值。例如,画面较暗的部分可以记录为较小的数,较亮的部分则可以记录为较大的数。图像处理软件利用这些数值构建画面。图像文件还可能压缩数据,以减少占用的空间;显示图像时,软件解释文件格式,并向屏幕提供所需的像素信息。
声音也是一个例子。麦克风会对空气压力的变化作出响应,电子电路把不断变化的信号测量为数字采样值。在一种常见的录音方式中,接近零的值代表较小的气压变化,离零更远的正值或负值代表较大的变化。较轻的声音通常对应较小的上下波动,较响的声音则对应较大的波动。软件可以存储、编辑或传输这些采样值。另一端的扬声器把电信号转换成空气中的新振动。并不是原来的声波被折叠起来塞进了内存;存储和处理的是描述声波的信息,随后又用这些信息产生了声音。
声音怎样变成数字
随时间变化的空气压力测量值
时间 →
这些例子用到的测量、规则和设备各不相同,却依赖同一种基本能力:计算机能用模式表示信息,按照指令处理模式,再通过实体设备产生结果。这足以把芯片内部只有两种状态的电路,与日常生活中的文字、照片和音乐联系起来。
出现问题时
我们前面讨论的例子是有意简化的。真正的应用程序需要解释复杂文件、处理意外数据,并作出许多决定。比如,要显示一张照片,应用程序就得先理解文件内容,才能把它变成画面。出错可能导致结果不正确,也可能让应用程序崩溃。有时程序试图访问没有权限使用的内存,计算机硬件和操作系统能够发现并阻止这种行为。但并非每个错误都会触发此类保护。应用程序也可能误用自己有权访问的内存,或者只是作出错误决定,却在没有明显警告的情况下继续运行。
有些错误比崩溃更严重。应用程序可能算错结果、丢失信息,或者跳过本该阻止某项操作的检查。回想费曼的档案员:他可能完全按照指令办事,但如果指令让他打开了错误的抽屉,结果照样会错。处理器只负责执行指令,并不知道它们是否符合程序员原本的意图。当应用程序错误地处理外部信息时,这些问题有时会以意料之外的方式影响其判断和行动,甚至带来安全风险。
在后续文章中,我们会探讨这些故障为何发生,以及工程师怎样预防它们,或限制它们造成的后果。
归根结底
一个比特记录两种可能状态中的一种:0 或 1。多个比特组合起来,计算机就能表示数字。按照约定的解释规则,这些数字又能代表字符、颜色或声音的测量值。处理器按照指令操作存储的信息,而输入和输出设备则把电子系统与外部世界联系起来。
再想想文档中出现的 X。字符编码决定它是哪个字母,字体和绘制文字的软件决定它长什么样,屏幕发出的光让你看见它。这些步骤全都依赖于可靠地表示和处理信息的能力。理解这条完整的过程,比想象计算机里藏着缩小版的文字和图片更有用。

