---
schema_version: "library-reader-v1"
source_type: "original_book"
title: "编码 隐匿在计算机软硬件背后的语言"
author: null
edition: null
publisher: null
year: null
isbn: "9787121181184"
library_document_id: "38270854-596f-4a26-8183-db8c968c6020"
source_sha256: "249786274d83909f0bddc34d77f81ed8f0b14d1eac8843c772a37a3ecbfe2cfc"
pdf_physical_page_count: 438
pdf_physical_page_index_base: 0
range_start: 390
range_count: 10
html_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020?start=390&limit=10"
markdown_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=390&limit=10"
previous_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=380&limit=10"
next_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=400&limit=10"
---

# 编码 隐匿在计算机软硬件背后的语言

> 页码规则：PDF_PHYSICAL_PAGE_INDEX 是从 0 开始的权威文件物理页索引；PRINTED_PAGE 是页面中识别出的印刷页码，可能为空，二者不得混用。

## PDF_PHYSICAL_PAGE_INDEX=390 | PRINTED_PAGE=374

指数0和255用于特殊的目的，稍后将简单介绍。如果指数的取值范围是1\~254，那么对于一个特定的数，可以用s（符号位)，e（指数）以及f（有效数）来描述它：

$$
( - 1 ) ^ { s } \times 1 . f \times 2 ^ { e - 1 2 7 }
$$

-1的s次幂是数学上所采用的一种巧妙的方法，它的含义是：如果 $\pmb { s } = 0$ ，则该数是正的(因为任何数的0次幂都是1)；如果 $s = 1$ ，则该数是负的(因为-1的1次幂等于-1)。

表达式的中间部分是1.f，其含义是：1的后面是小数点，小数点后面跟着23位的有效数。1.f与2的幂相乘，其中指数等于内存中的8位的偏移指数减去127。

注意，目前为止我们还没有学习如何表达那个经常遇到却又总被遗忘的一个数字：那就是 $" 0 "$ 。这是一种特殊的情况，下面我们对其进行说明。

如果 $e = 0$ 且 $f = 0$ ，则该数为 $0 _ { \circ }$ 在这种情况下，通常把32位都设置为0以表示该数为 $0 _ { \circ }$ 但是符号位可以设置为1，这种数可以解释为负 $0 _ { \circ }$ 负0可以用来表示非常小的数，这些数极小以至于不能在单精度格式下用数字和指数来表示，但它们仍然小于 $0 _ { \circ }$

如果 $e = 0$ 且 $f \neq 0$ ，则该数是合法的，但不是规范化的。这类数可以表示为：

$$
( - 1 ) ^ { s } \times 0 . f \times 2 ^ { - 1 2 7 }
$$

注意，在有效数中，小数点的左边是 $\scriptstyle \mathbf { 0 } _ { \circ }$

如果 $e = 2 5 5$ 且 $f = 0$ ，则该数被解释为无穷大或无穷小，这取决于符号位s的值。

如果 $e = 2 5 5$ 且 $f \neq 0$ ，则该值被解释为“不是一个数”，通常被缩写为NaN（notanumber)。NaN用来表示未知的数或非法操作的结果。

单精度浮点格式下，可以表示的规格化的最小正、负二进制数是：

$$
1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
$$

小数点后面跟着23个二进制 $\scriptstyle \mathbf { 0 } _ { \circ }$ 单精度浮点格式下，可以表示的规格化的最大正、负二进制数是：

$$
1 . 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 \times 2 ^ { 1 2 7 }
$$

在十进制下，这两个数近似地等于 $1 . 1 7 5 4 9 4 3 5 1 \times 1 0 ^ { - 3 8 }$ 和 $3 . 4 0 2 8 2 3 4 6 6 \times 1 0 ^ { 3 8 }$ ，这也就是

编码隐匿在计算机软硬件背后的语言

374

## PDF_PHYSICAL_PAGE_INDEX=391 | PRINTED_PAGE=3754

单精度浮点数的有效表示范围。

如前所述，10位二进制数可以近似地用3位十进制数来表示。其含义是，如果把10位都置为1，即十六进制的3FFh或十进制的1023，它近似等于把十进制数的3位都置为9，即999，可以表示为下面的约等式：

$$
2 ^ { 1 0 } \approx 1 0 ^ { 3 }
$$

两者之间的这种关系意味着：单精度浮点数格式存放的24位二进制数大体上与7位的十进制数相等。因此，可以说单精度浮点格式提供24位的二进制精度或者7位的十进制精度。其深层的含义是什么呢？

当我们查看定点数时，其精确度是很明显的。例如，当我们表示钱款时，采用两位定点小数就可以精确到美分。但是对于采用浮点格式的数，就不能如此肯定了。其精确度依赖于指数的值，有时候浮点数可以精确到比美分还小的单位，但有时候其精确度甚至达不到美元。

这样说可能更合适：单精度浮点数的精度为 $1 / 2 ^ { 2 4 }$ ，或1/16777216，或百万分之六，但其真正的含义是什么呢？

首先，这意味着在单精度浮点格式下，16,777,216和16,777,217将表示成同一个数。不仅如此，处于这两个数之间的所有的数（例如，16,777,216.5）也将被表示成同一个数。所以上面提到的3个十进制数都按32位单精度浮点数：

4B800000h

来存放。将该数按符号位、指数位和有效数位划分，可以表示为：

$$
0 \ 1 0 0 1 0 1 1 1 \ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
$$

也就是：

$$
1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 _ { 2 } \times 2 ^ { 2 4 }
$$

下一个二进制浮点数可表示的最大有效数是16,777,218，即：

$$
1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 _ { 2 } \times 2 ^ { 2 4 }
$$

23 定点数和浮点数

3754

## PDF_PHYSICAL_PAGE_INDEX=392 | PRINTED_PAGE=376

以同一个浮点数来表示两个不同的十进制数，有时可能成为一个问题，也可能不会。

但如果你为银行编写程序，用单精度浮点数来存放以美元、美分为单位的数字时，就会发现262144.00美元和262144.01美元在计算机中存储为同一个数：

$$
1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 _ { 2 } \times 2 ^ { 1 8 }
$$

这也是为什么人们在处理以美元、美分表示的钱款数目时更愿意使用定点数的一个原因。当使用浮点数时，你会发现它还存在着一些让人崩溃的小问题。你的程序进行了一系列计算，应该得到的结果为3.50的，但由于使用浮点数，你得到的可能是3.499999999999。这种问题在浮点数运算中经常发生，而且没有一套完整的解决方案。

如果想在程序中使用浮点格式数，但使用单精度格式又会出现各种问题，这时你可以考虑使用双精度浮点数（double-precision floating-point format)。这种类型的数需要用8个字节来表示，它的结构如下表所示。

双精度浮点数的指数偏移量是1023，或十六进制的3FFh，因此以该格式存储的数可以表示为：

$$
( - 1 ) ^ { s } \times 1 . f \times 2 ^ { e - 1 0 2 3 }
$$

上面提到的关于单精度浮点格式下的0，无穷大（小）和NaN的判断规则同样适用于双精度浮点格式。

双精度浮点格式下可以表示的最小正数或负数为：

$$
1 . 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
$$

注意，小数点的后面共有52个 $\mathbf { 0 } _ { \circ }$ 同样的，可以表示的最大数为：

$$
1 . 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
$$

其所能表示的范围，用十进制可以近似记为：

$$
2 . 2 2 5 0 7 3 8 5 8 5 0 7 2 0 1 4 \times 1 0 ^ { - 3 0 8 } \mathrm { { \sim } } 1 . 7 9 7 6 9 3 1 3 4 8 6 2 3 1 5 8 \times 1 0 ^ { 3 0 8 }
$$

10的308次幂是一个非常巨大的数，在1的后面跟着308个0。

编码隐匿在计算机软硬件背后的语言

376

## PDF_PHYSICAL_PAGE_INDEX=393 | PRINTED_PAGE=377

双精度浮点格式的有效数有53位（包括前面没有列出的那一位），大致相当于十进制的16位。与单精度浮点格式相比，这已经有了很大的改进了，但仍然不能避免两个不同的数存储为同一个结果的情况。例如，140,737,488,355,328.00和140,737,488,355,328.01在内存中存放时，会被当做同一个数来处理，它们的双精度浮点格式表示为：

42E0000000000000h

即：

1.0000000000000000000000000000000000000000000000000000₂×247

当然，为浮点数发明一种在内存中的存储方式，这只是在汇编程序使用浮点数所涉及的工作的一小部分。如果你决定闭门造车，完全独立地开发一台计算机，则必须要独立编写用于浮点数加、减、乘、除运算的函数集。幸运的是，有了前面关于整数四则运算的学习，这些关于浮点数的运算就可以分解成许多小的关于整数的加、减、乘、除运算，这样就能将问题大大简化。

例如，浮点数加法中最重要一点的就是如何对有效数相加，为了能使它们的有效位匹配，需要利用指数来确定对其如何移位。假设要进行下面的加法运算：

$$
( 1 . 1 1 0 1 \times 2 ^ { 5 } ) + ( 1 . 0 0 1 0 \times 2 ^ { 2 } )
$$

你需要把有效数部分的11101和10010相加，但并不是简单地直接相加。两个数的指数部分的不同决定了第二个数必须相对于第一个数右移。实际上我们要做的整数加法应该是11101000加10010。最后的运算结果是：

$$
1 . 1 1 1 1 0 1 0 \times 2 ^ { 5 }
$$

前面我们曾列出过太阳与地球的距离以及氢原子的半径，如果把这两个数相加会是怎样的结果呢？显而易见，因为它们两者的指数相差太大，因此较小的数对结果甚至没有影响。

两个浮点数的乘法意味着要把有效数当作整数相乘，并且把指数部分相加。为了使结果规范化，一般需要对指数调整一到两次。

浮点数运算另一层次的复杂性体现在处理一些较为繁杂的函数运算，例如平方根、

23 定点数和浮点数

377

## PDF_PHYSICAL_PAGE_INDEX=394 | PRINTED_PAGE=378

指数、对数和三角函数。但所有的这些运算都可以通过加、减、乘、除这四种基本的浮点数运算来实现。

例如，三角函数中的sin函数可以通过下面的一系列展开式近似计算：

$$
\sin ( x ) = x - { \frac { x ^ { 3 } } { 3 ! } } + { \frac { x ^ { 5 } } { 5 ! } } - { \frac { x ^ { 7 } } { 7 ! } } + \cdots
$$

参数x的值必须是弧度， $3 6 0 ^ { \circ }$ 对应的弧度范围是 $2 \pi _ { \circ }$ 上式中的感叹号表示阶乘运算符，它的意义是把从1到该数之间的所有整数相乘，例如 $5 ! = 1 \times 2 \times 3 \times 4 \times 5$ 。这只是简单的乘法运算，每一项的指数部分也是乘法运算。其余的部分也只是简单的除法，加法或减法运算，这些都是容易实现的。上面的算式中，唯一让人感到棘手的地方是最后的省略部分，这意味着计算会一直继续下去。然而事情并没有想象中的那么糟糕，在实际运算中，如果把弧度的取值限制在 $\scriptstyle 0 \sim \pi / 2$ 的范围内（从这个范围就可以推导出所有的正弦值)，你根本不需要进行多少运算，因为大约展开12项后，就可以使结果精确到双精度浮点数要求的53位。

当然，使用计算机的目的就是帮助人们更加方便地解决问题，而编写程序来进行浮点数运算这一繁杂工作似乎和这个目的背道而驰。但这正是软件的优势所在：-旦某个人为特定的计算机编写了浮点数运算的程序，那么其他的人都可以使用它。浮点数运算在科学和工程类程序中极为重要，因此常常被赋予很高的优先级。在计算机发展的早期，为新制造的计算机做的第一项工作就是为其编写浮点数运算程序。

实际上，甚至可以直接利用计算机机器码指令来实现浮点数的运算。当然，实际做起来要比“动动嘴皮子”困难得多，但这也从另一个方面说明了浮点数运算的重要性。如果可以在硬件上实现浮点数算术运算—类似在16位微处理器上进行乘法和除法运算—则该机器上的所有的浮点数运算都会变得更快。

IBM公司在1954年发布了IBM704，它是第一台将浮点数运算硬件作为可选配件的商用计算机，该机器以36位空间来存储所有的数。对于浮点数而言，其36位被分成27位的有效数，8位的指数和1位的符号位。浮点运算硬件可以直接进行加法、减法、乘法和除法运算。其他的浮点运算则必须通过软件来实现。

从1980年开始，浮点运算硬件开始应用于桌面计算机，这起始于英特尔当年发布的

编码——隐匿在计算机软硬件背后的语言

378

## PDF_PHYSICAL_PAGE_INDEX=395 | PRINTED_PAGE=379

8087数字协同处理（Numeric Data Coprocessor）芯片，当时这种集成电路被称做数学协同处理器（math coprocessor）或浮点运算单元（floating-point unit，FPU)。8087 不能独立工作，它只能与8086或8088（Intel的首个16位微处理器）芯片一起工作，因此被称做协处理器。

8087拥有40个管脚，它使用的很多信号与8086或8088完全相同。微处理器和数学协处理器通过这些信号相连。当CPU读取到一条特殊指令ESC（Escape）时—协同处理器开始接管控制权并执行下一条机器指令，该指令可以是三角函数、指数和对数等68条指令中的任一条。它所处理的数据类型遵循IEEE标准。在当时，8087被认为是市面上最高水平的集成电路。

可以把协处理器当做一个小型的自包含计算机。当响应某个特定的浮点运算机器码指令时（例如FSQRT指令，它用来计算平方根)，协处理器会以固有的方式执行存放在ROM中属于自己的指令序列。这些内部指令称做微代码（microcode）。通常，这些指令都是循环的，因此不能立即得到最终的结果。虽然如此，但数学协处理器在运算速度方面仍然表现优异，与软件方法相比，其速度至少是后者的10倍。

在最初版本的IBMPC主板上，位于8080芯片的右边有1个40个管脚的插槽供8087芯片接入。但令人失望的是，这个插槽是空的，用户如果需要进行浮点数运算就必须单独购置一块8087芯片，并将其插入主板后才能使用。安装数学协处理器并不能提高所有应用程序的运行速度，因为有些应用程序比如文字处理程序—几乎用不到浮点数运算。其他应用程序，比如电子表格处理程序，对浮点数运算依赖程度很高，在安装了数学协处理器之后，它们的执行速度有很大的提高，但并非所有的程序都是如此。

可以看到，在安装了数学协处理器后，程序员必须使用协处理器机器码指令来编写特殊的代码，因为数学协处理器不是标准硬件，因此它只能执行这些特殊的代码。而这些工作让程序员烦不胜烦。尽管他们不愿意，他们仍不得不编写自己的浮点数运算子程序(因为大多数人并没有安装数学协处理器)，因此这就多了一个额外的工作一个并不轻松的工作在程序中支持8087芯片。最后就出现了这样的局面：如果机器上安装了数学协处理器，程序员就要学会编写相应的应用程序以支持它的运行；如果没有安装，程序员就要通过编程来模拟它进行浮点数的运算。

在随后的几年内，英特尔还发布了与286配合工作的287数学协处理器，与386配合工作的387数学协处理器。但是，在1989年发布的486DX芯片中，FPU已经内建在CPU的结构里，它不再作为一个配件供选择安装了。令人失望的是，在1991年发布的一款低端芯片486SX中，英特尔没有为该其内建FPU，而是提供了一块可选的487SX数学协处理芯片。但1993年发布的奔腾芯片中，CPU内置FPU再次成为标准，也许这是永远的标准。在1990年发布的68040芯片中，摩托罗拉首次将FPU集成在CPU中，在此之前，摩托罗拉发布了68881和 68882数学协处理器来支持 68000家族早期的微处理器。PowerPC芯片同样使用了内置浮点数运算硬件的技术。

23 定点数和浮点数

379

## PDF_PHYSICAL_PAGE_INDEX=396 | PRINTED_PAGE=380

浮点数运算硬件对于困惑的汇编程序员来说无疑是个惊喜的礼物，但相对于20世纪50年代开始的某些其他工作来言，这只是历史所迈出的一小步。接下来，我们的探索之旅即将到达下一站：计算机语言。

编码隐匿在计算机软硬件背后的语言

380

## PDF_PHYSICAL_PAGE_INDEX=397 | PRINTED_PAGE=NOT_DETECTED

高级语言与低级语言

使用机器码编写程序就如同用牙签吃东西，伸出手臂使出较大的力气刺向食物，但每次都只获取到小小的一块，这个过程是辛苦且漫长的。同样的，每个机器码字节所能完成的工作，是你能想象到的最微小且最简单的工作一从内存获取一个数，之后加载到处理器，再把它与另一个数相加，最终将运算结果保存到内存等正因如此，很难想象如何使用这些机器码构成一个完整的程序。

目前为止，至少对于在第22章讨论的原始模型阶段来说，我们已经取得了一定的进步，在那个阶段，我们使用过控制面板上的开关将二进制数据输入到存储器。在第22章中，介绍了如何编写一段简单的程序，让我们可以利用键盘将十六进制机器码输入计算机，以及通过视频显示设备来检查这些代码。这种改进固然可取，但仍不是我们的终极目标。

前面的章节介绍过，可以使用某些较短的助记符来关联机器码字节，这些助记符包括MOV，ADD，CALL，HLT等，通过这些类似英语的符号我们可以较方便地引用机器码。通常这些助记符的后面会跟着操作数，这可以进一步指明它所关联的机器码指令的功能。例如8080机器码字节46h，它的功能是令处理器将存储在内存特定地址的字节转移至寄存器B，而该地址由寄存器对HL中的16位数寻址。这个操作可以简单地写做：

24

## PDF_PHYSICAL_PAGE_INDEX=398 | PRINTED_PAGE=382

显然，使用汇编语言编写程序要比使用机器语言简单得多，但微处理器并不能解释汇编语言。在前面的章节中我们已经学习了如何在纸上编写汇编程序，但只有当你确实准备在微处理器上运行汇编程序，才会手工对其汇编，这样就可以将汇编语言程序的语句转换成了机器语言代码，并把它们输入内存。

当然，我们希望最好由计算机能独自完成语言转换的工作。如果你的8080计算机正在运行CP/M操作系统，而且你已经拥有了所有必需的工具，那就再好不过了，因为下面我们将介绍其工作原理。

第一步，建立一个文本文件，并将汇编语言程序输入到该文本文件中。这项工作可以使用CP/M的应用程序ED.COM来完成。该程序是一个可以用来创建、修改文本文件的编辑器。假设你把该文本文件命名为PROGRAM1.ASM，其中ASM是文件类型，用来指明该文本文件的内容是由汇编语言程序组成。这个文件的内容如下：

这个文件中有两条语句我们从未接触过。第一条语句是ORG（origin)，它不与任何8080指令对应，其功能是用来指明下面语句的地址从0100h地址处开始。如前所述，该地址是CP/M将程序装入内存的起始地址。

第二条语句是 LXI（Load Extended Immediate）指令，其功能是将一个16 位数加载到寄存器对DE。在本例中，该16位数是由标记Text提供的。该标记在程序底端的附近，位于DB（DataByte）语句之前。DB语句我们也是第一次遇到，其后可以跟着一些字节，这些字节以逗号分隔或者用单引号括起来（如本例）。

编码隐匿在计算机软硬件背后的语言

382

## PDF_PHYSICAL_PAGE_INDEX=399 | PRINTED_PAGE=383

MVI(Move Immediate）语句将数值 9 转移到寄存器 C。CALL 5 语句实现 CP/M 的函数调用功能。函数5的作用是：显示以寄存器对DE给出的地址为起始处的字符串，直到遇到\$结束（可以看到，在程序的结尾处使用了美元符号“\$”作为文本的结束标志，这种方式看起来很奇怪，但CP/M就是采取的这种方式)。最后的RET语句用来结束程序，并把控制权交还给CP/M（实际上，这只是结束CP/M程序的方法之一)。END语句用来指明汇编语言文件已经结束。

现在我们已经有了一个包含7行语句的文本文件，下一步要做的就是对其进行汇编，即将其转换成机器语言代码。以前这项工作是通过手工完成的，但现在我们的机器运行的是CP/M系统，可以利用CP/M中一个叫做 ASM.COM的模块来完成这项工作。该模块是 CP/M的汇编器(assembler)。可以在 CP/M的命令行中使用下面的语句运行 ASM.COM文件：

ASM PROGRAM1.ASM

ASM 对 PROGRAM1.ASM 文件进行汇编，产生一个名为 PROGRAM1.COM的新文件，PROGRAM1.COM包含了与我们编写的汇编程序相对应的机器码（实际上，该过程还包含另一个步骤，但在该操作中并不重要)。现在就可以使用CP/M的命令行来运行PROGRAM1.COM文件，程序运行的结果是显示字符串“Hello!”然后结束。

PROGRAM1.COM文件包含以下16个字节：

$$
\begin{array} { r l r l r l r l r l r l r l r l r l } { { 1 } 1 } & { { } 0 9 } & { 0 1 } & { 0 \mathrm { E } } & { 0 9 } & { { } \mathrm { C D } } & { 0 5 } & { { } 0 0 } & { { } \mathrm { C 9 } } & { 4 8 } & { { } 6 5 } & { { } 6 \mathrm { C } } & { { } 6 \mathrm { C } } & { { } 6 \mathrm { F } } & { { } 2 1 } & { { } 2 4 } \end{array}
$$

开始的3个字节是LXI指令，其后的两个字节是MVI指令，接下来的三个字节是CALL指令，紧随其后的一个字节是RET指令，最后的7个字节是ASCII码，包括5个字母“Hello”，感叹号“!”以及美元符号“\$”。

像ASM.COM这样的汇编器程序所做的工作是：读取一个汇编语言文件(source-code，通常称做源代码文件)，将其转换得到一个包含机器码的文件—可执行文件（executablefile)。从宏观的角度来看，汇编器是非常简单的，因为构成汇编语言的助记符和机器码之问是一-对应的。汇编器拥有-一张包括所有可能助记符及其参数的表，它逐行读取汇编语言程序，把每一行都分解成为助记符和参数，然后把这些短小的单词和字符与表中的内容匹配。通过这种匹配的过程，每一个语句都会找到与其对应的机器码指令。

24 高级语言与低级语言

383

下一段：[继续读取](https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=400&limit=10)
