---
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: 300
range_count: 10
html_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020?start=300&limit=10"
markdown_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=300&limit=10"
previous_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=290&limit=10"
next_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=310&limit=10"
---

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

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

## PDF_PHYSICAL_PAGE_INDEX=300 | PRINTED_PAGE=284

例如，指令

对应的操作码为：

用十六进制数可表示为6Bh。这与前面列出的表格是一致的。

可以设想一下，在8080的内部可能是这样的：标记为sss的3位用于8-1数据选择器，标记为ddd的3位用来控制3-8译码器以此确定哪一个寄存器锁存了值。

寄存器B和C也可以组合成16位的寄存器对BC，同样我们还可以用D和E组成寄存器对DE。如果这些寄存器对也包含要读取或保存的字节的存储器地址，则可以用下面的指令实现：

另一种类型的传送（Move）指令称做传送立即数（MoveImmediate），它的助记符写做MVI。传送立即数指令是一个双字节指令，第-个字节为操作码，第二个是数据。这个单字节数据从存储器转移到某个寄存器，或者转移到存储器中的某个存储单元，该存储单元由HL寄存器对寻址。

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

284

## PDF_PHYSICAL_PAGE_INDEX=301 | PRINTED_PAGE=285

例如，当指令：

MVI E, 37h

执行后，寄存器E存放的字节是37h。这就是我们要介绍的第三种寻址方式一立即数寻址(immediate addressing)。

下面将列出一个操作码集，包括32个操作码，它们能完成4种基本的算术运算，这些运算在第17章设计处理器时我们已经熟悉了，它们是加法(ADD)、进位加法(ADC)、减法（SUB）和借位减法（SBB）。可以看到，在所有的例子中，累加器始终用于存放其中的一个操作数，同时用来保存计算结果。这些指令如下。

19两种典型的微处理器

285

## PDF_PHYSICAL_PAGE_INDEX=302 | PRINTED_PAGE=286

假如累加器A存放的字节是35h，累加器B存放的字节是22h，经过减法运算：SUB A, B

累加器中的值变为22h，即两个字节的差。

如果累加器A中的值为35h，寄存器H和L中的值分别是10h和7Ch，而存储器地址107Ch处的字节为4Ah，指令：

ADD A, [HL]

把累加器中的值（35h）与寄存器对HL寻址（107Ch）存储器得到的数值（4Ah）相加，并把计算结果（7Fh）保存到累加器。

在8080中，使用ADC指令和SBB指令可以对16位数、24位数、32位数甚至更高位的数进行加法、减法运算。例如，假设现在寄存器对BC和DE各自保存了一个16位的数，我们要把这两个数相加，并且把结果保存在寄存器对BC中。具体做法如下：

MOV A, C ；低字节操作   
ADD A, E   
MOV C, A   
MOV A, B ；高字节操作   
ADC A, D   
MOV B, A

在上面的计算中，用ADD指令对低字节相加，用ADC指令对高字节相加。低字节相加产生的进位会进入高字节的运算中。在这段简短的代码中，我们用到了4个MOV指令，这是因为在8080中只能利用累加器进行加法运算，操作数在累加器和寄存器之间来回地传送，因此在8080的代码中会大量使用MOV指令。

现在我们来讨论8080的标志位（flag）。第17章设计的处理器已经有了CF（进位标志位）和ZF（零标志位）两个标志位，在8080中又新增了3个标志位，包括符号标志位SF，奇偶标志位PF和辅助进位标志位AF。在8080中，用一个专门的8位寄存器来存放所有标志位，该寄存器称做程序状态字（Program Status Word，PSW)。不同的指令对标志位有不同的影响，LDA、STA或MOV指令始终都不会影响标志位，而ADD、SUB、ADC以及SBB指令会影响标志位的状态，具体情况如下。

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

286

## PDF_PHYSICAL_PAGE_INDEX=303 | PRINTED_PAGE=287

如果运算结果的最高位是1，那么符号标志位SF标志位置1，表示该计算结果是负数。

如果运算结果为0，则零标志位ZF置0。

如果运算结果中“1”的位数是偶数，即具有偶数性（evenparity)，则奇偶标志位PF置1；反之，如果“1”的位数是奇数，即运算结果具有奇数性（odd parity)，则PF置0。由于PF的这个特点，有时会被用来进行简单的错误检查。PF在8080程序中并不常用。

进位标志位 CF的情况和第17章描述的稍有不同，当 ADD和ADC运算产生进位或者SUB和SBB 运算不发生借位时，CF 都置1。

辅助进位标志位AF只有在运算结果的低4位向高4位有进位时才置1。它只用于DAA（Decimal Adjust Accumulator，十进制调整累加器）指令中。

下面的两条指令会直接影响进位标志位 $\mathbf { C F } _ { \circ }$

第17章设计的计算机可以执行 ADD、ADC、SUB和 SBB指令（虽然缺乏灵活性），而8080功能更为强大，它还可以执行AND（与）、OR（或）、XOR（异或）等逻辑运算。不论是算术运算还是逻辑运算，都是由8080处理器的算术逻辑单元（ALU）来完成的。

以下是8080的算术运算和逻辑运算指令。

19 两种典型的微处理器

287

## PDF_PHYSICAL_PAGE_INDEX=304 | PRINTED_PAGE=288

AND、XOR和 OR都是按位运算（bitwise operations）指令，也就是说对于这些逻辑运算指令，其操作数的每一个对应位都是独立运算的，例如：MVI A, OFhMVI B, 55hAND A, B

保存到累加器的结果将会是05h。假如我们把3条指令换作OR，则最终的结果将会是5Fh；  
如果换作XOR，则结果又变成了5Ah。CMP（Compare，比较）指令同 SUB指令类似，也是把两个数相减，不同之处在于  
它并不在累加器中保存计算结果，计算的目的是为了设置标志位。这个标志位的值可以  
告诉我们两个操作数之间的大小关系。例如，我们考虑下面的指令：MVI B, 25hCMP A, B

指令执行后，累加器A中的值并没有变化。改变的是标志位的值，如果A中的值等于25h，则零标志位ZF置1；如果A中的值小于25h，则进位标志位CF置1。

同样的，也可以对立即数进行这8种算术逻辑操作。

例如，可以用下面的这条指令来替代上面列出的两条指令：CPI A, 25h

下面是两种特别的8080指令。

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

288

## PDF_PHYSICAL_PAGE_INDEX=305 | PRINTED_PAGE=289

CMA 是 Complement Accumulator的简写。它对累加器中的数按位取反，即把0 变为1，1变为0。例如，累加器中的数如果是01100101，使用CMA命令后，累加器中的数按位取反，得到10011010。我们还可以使用如下指令对累加器中的数取反：

XRI A, FFh

前面提到过，DAA是Decimal Adjust Accumulator的缩写，即十进制调整累加器，它可能是8080中最复杂的一条指令。在8080微处理器中专门设计了一个完整的小部件用来执行该指令。

DAA指令提供了一种用二进制码表示十进制数的方法，称为 BCD码（binary-codeddecimal)，程序员可以在该指令的帮助下实现十进制数的算术运算。BCD码采用的表示方式为，每4位为一段，每段所能表示数的范围是：0000\~1001，对应十进制数的0\~9。因为1字节有8位故可分割为2个段，因此在BCD码格式下，一个字节可以表示两位十进制数。

假设累加器A存放的是BCD码表示的27h，显然它就对应十进制数的27（通常，十六进制的27h 对应的十进制数是39)。同时假设寄存器B 中存放着 BCD 码表示的 94h。假如执行如下指令：

MVI A, 27h MVI B, 94h ADD A, B

累加器中存放的最终结果是 BBh，当然，这肯定不是 BCD码。因为BCD码中每4位组成的段所能表示的十进制数不会超过9。然而，当我们执行指令：

DAA

那么累加器最后所保存的值是21h，而且进位标志位CF置1。因为十进制的27与94相加的结果为121。由此可以看到，使用BCD码进行十进制的算术运算是很方便的。

在8080程序中，经常会对一个数进行加1或减1运算。在第17章的乘法程序中，为了实现对一个数减1，我们把该数与FFh相加，它是-1的补码。8080提供了专门的指令用来对寄存器或存储器中的数进行加1（称作增量）或减1（称作减量）操作。

19 两种典型的微处理器

289

## PDF_PHYSICAL_PAGE_INDEX=306 | PRINTED_PAGE=290

INR和DCR都是单字节指令，它们可以影响除CF（Carry Flag）之外的所有标志位。

8080还包括4个循环移位（Rotate）指令，这些指令可以把累加器中的内容向左或向右移动1位，它们的具体功能如下。

这些指令只对进位标志位CF有影响。

假设累加器中存放的数是A7h，即二进制的10100111。RLC指令使其每一位都向左移一位。最终的结果是，最低位（左端为低位，右端为高位）移出顶端移至尾部成为最高位，这条指令也会影响CF的状态。在这个例子中CF置1，最后的结果为01001111。RRC指令以同样的方式进行右移位操作。如果移位之前的数是10100111，执行RRC之后将变为11010011，同时CF置1。

较之 RLC 和 RRC，RAL 和 RAR 指令的工作方式稍有不同。执行 RAL指令时，累加器中的数仍然按位左移，把CF中原来的值移至累加器中数值的最后一位，同时把累加器中数据的原最高位移至CF。例如，假设累加器中移位之前的数是10100111且CF为0，执行RAL指令后，累加器中的数变为01001110而CF变为1。类似的，如果执行的是RAR指令，累加器中的数变为01010011而CF变为1。

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

290

## PDF_PHYSICAL_PAGE_INDEX=307 | PRINTED_PAGE=291

当我们在程序中需要对某个数进行乘2（左移）或除2（右移）运算时，使用移位操作会使运算变得非常简单。

我们通常把微处理器可以寻址访问的存储器称为随机访问存储器（random accessmemory，RAM)，主要的原因是：只要提供了存储器地址（有多种方式)，微处理器可以用非常简便的方式访问存储器的任意存储单元。RAM就像一本书，我们可以翻到它的任意一页。这种方式很方便，它不像存储在一个微缩胶片上的整个星期的报纸，为了阅读星期六的内容，我们需要扫描几乎整个星期的内容。同样，它也比读取磁带快得多，因为当我们要听最后一首歌时，需要快进磁带的一整面。微缩胶片和磁带都不是随机访问的，它们是顺序访问（sequential access）的。

显然，随机访问存储器是非常好的一种寻址方式，对于经常访问存储器的微处理器来说更是如此。然而，在某些情况下使用不同的寻址方式访问存储器也是有好处的。例如，下面例子中的这种存储方式既不是随机的也不是顺序的：假设你在办公室工作，有人会到你办公桌前为你分配任务，每一项工作都用到某种文件夹。这些工作通常有这样的特点，在你完成某项工作之前首先要做另一项相关工作，并用到另一个文件夹。因此你只能放下第一个文件夹，并在它上面打开一个第二个文件夹继续工作。现在又有一个人给你分配了一个比前一项优先级更高的工作，于是你打开第三个文件夹放在前面两个上，继续工作。而这项工作也需要先做一项相关工作，于是你只好再打开第四个文件夹，现在你的办公桌上已经堆叠了四个文件夹了。

你可能已经注意到了，事实上，这些堆叠的文件夹很有序地保存了你干活的顺序轨迹。最上面的文件夹总是代表优先级最高的工作，完成该工作之后就可以做接下来的工作了，依此类推。最后当你处理完办公桌上最后一个文件夹（即接受的第一个任务）后，就可以回家了。

这种形式的存储器称作堆栈（stack)。使用堆栈时，我们以从底部到顶部的顺序把数据存入堆栈，并以相反的顺序把数据从堆栈中取出，因此该技术也称作后进先出存储器(last-in-first-out，LIFO)。堆栈的特点是，最先保存到堆栈中的数据最后被取出，而最后保存的数据则被最先取出。

同样，在计算机中也可以使用堆栈，当然计算机中的堆栈保存的是数据而不是工作。大量的实践证明，在计算机中使用堆栈技术是十分方便的。通常把将数据存入堆栈的过程称作压入（push），把从堆栈取出数据的过程称作弹出（pop）。

19 两种典型的微处理器

291

## PDF_PHYSICAL_PAGE_INDEX=308 | PRINTED_PAGE=292

假设你正在编写一个汇编语言程序，需要用到寄存器A、B、C来存储数据。在编写程序的过程中，你注意到程序需要做一个小的计算，并且该计算也需要用到寄存器A、B、C。你希望在完成该计算之后仍然回到原来的地方，并且仍然使用寄存器A、B、C中原先存放的数据。

为了保存寄存器A、B、C原先存放的数据，可以简单地把这些数据保存到存储器中不同的地址中，需要进行的计算完成之后，再把这些数据从存储器转移到寄存器。但这种方式需要记录数据存放的地址。有了堆栈的概念之后，我们可以用一种更清晰的方式来处理这个问题，即把这些寄存器中的数据依次存放到堆栈中。

PUSH A PUSH B PUSH C

稍后将具体解释这些指令实际的意义。现在需要知道的是，这些指令以某种方式把寄存器中的内容保存到先进后出存储器。这些指令执行之后，寄存器中原有的数据将妥善地保存下来，你就可以放心地使用这些寄存器进行别的工作了。为了取回原来的数据，可以使用POP指令把它们从堆栈中弹出，当然弹出的顺序和原来压入的顺序是相反的。相应的POP指令如下所示。

POP C POP B POP A

再次谨记：后进先出。如果POP指令的顺序弄错了，将会引起严重的错误。

我们可以在程序中多次用到堆栈而不会引起混乱，这是堆栈机制的一个特殊优势。例如，在我们要编写的程序中已经把寄存器A、B、C中的数保存到了堆栈，在程序的另一段又需要把寄存器C、D、E中的数保存到堆栈，可以使用下面的指令：

PUSH C PUSH D PUSH E

当然，在该段程序中还需要使用一些指令将保存到堆栈中的数据取回至寄存器，这些指令是：

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

292

## PDF_PHYSICAL_PAGE_INDEX=309 | PRINTED_PAGE=293

显然，由于先进后出的原则，这些数据在先前存放的C、B、A中的数据之前弹出堆栈。

堆栈的功能是怎样实现的呢？首先，堆栈其实就是一段普通的RAM存储空间，只是这段空间相对独立不另作他用。8080微处理器设置了一个专门的16位寄存器对这段存储空间寻址，这个特殊的寄存器称为堆栈指针（SP，Stack Pointer)。

在我们所举的例子中，对于8080来说使用PUSH和POP对寄存器操作实际上是不准确的。在8080中，执行PUSH指令实际上是把16位的数据保存到堆栈，执行POP指令是把这些数据从堆栈中取回至寄存器。因此，对于上面的如PUSHC，POPC等指令，我们对其进行如下的修改。

PUSH BC 指令将寄存器 B 和 C 中的数据保存到堆栈，而 POP BC 则将这些数据从堆栈取回到寄存器B和C中，并且保持原来的顺序。最后一行指令中的PSW代表程序状态字，如前所述，这是一个8位的寄存器，用于保存标志位。最后一行的PUSH和POP指令的操作对象实际上是累加器和PSW，即压入和弹出堆栈的数据由累加器和PSW中的内容组成。如果你想把所有寄存器中的数据及全部标志位都保存到堆栈，可以使用下面的指令：

PUSH PSW

PUSH BC

PUSH DE

PUSH HL

堆栈是怎样工作的呢？我们假设堆栈指针是8000h，当执行PUSH BC指令时将会引发以下操作。

堆栈指针减1，变为7FFFh。

19 两种典型的微处理器

POP E POP D POP C

293

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