第1章 预备知识

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

第1章 预备知识

本章介绍汇编语言的一些基本概念，给出一些用汇编语言编程所需要的基本知识。

1.1 汇编语言的由来及其特点

1.1.1 机器语言

机器指令是CPU能直接识别并执行的指令，它的表现形式是二进制编码。机器指令通常由操作码和操作数两部分组成，操作码指出该指令所要完成的操作，即指令的功能，操作数指出参与运算的对象，以及运算结果所存放的位置等。

由于机器指令与CPU紧密相关，所以，不同种类的CPU所对应的机器指令也就不同，而且它们的指令系统往往相差很大。但对同一系列的CPU来说，为了满足各型号之间具有良好的兼容性，要做到：新一代CPU的指令系统必须包括先前同系列CPU的指令系统。只有这样，先前开发出来的各类程序在新一代CPU上才能正常运行。

机器语言是用来直接描述机器指令、使用机器指令的规则等。它是CPU能直接识别的唯一一种语言，也就是说，CPU能直接执行用机器语言描述的程序。

用机器语言编写程序是早期经过严格训练的专业技术人员的工作，普通的程序员一般难以胜任，而且用机器语言编写的程序不易读、出错率高、难以维护，也不能直观地反映用计算机解决问题的基本思路。

由于用机器语言编写程序有以上诸多的不便，现在几乎没有程序员这样编写程序了。

1.1.2 汇编语言

虽然用机器语言编写程序有很高的要求和许多不便，但编写出来的程序执行效率高，CPU严格按照程序员的要求去做，没有多余的额外操作。所以，在保留“程序执行效率高”的前提下，人们就开始着手研究一种能大大改善程序可读性的编程方法。

为了改善机器指令的可读性，选用了一些能反映机器指令功能的单词或词组来代表该机器指令，而不再关心机器指令的具体二进制编码。与此同时，也把CPU内部的各种资源符号化，使用该符号名也等于引用了该具体的物理资源。

如此一来，令人难懂的二进制机器指令就可以用通俗易懂的、具有一定含义的符号指令来表示了，于是，汇编语言就有了雏型。现在，我们称这些具有一定含义的符号为助忆符，用指令助忆符、符号地址等组成的符号指令称为汇编格式指令(或汇编指令)。

汇编语言是汇编指令集、伪指令集和使用它们规则的统称。伪指令是在程序设计时所需要的一些辅助性说明指令，它不对应具体的机器指令，有关内容在以后的各章节中会有详细叙述，在此不展开介绍。

用汇编语言编写的程序称为汇编语言程序，或汇编语言源程序，在本教材中或特定的环境下，也可简称为源程序。汇编语言程序要比用机器指令编写的程序容易理解和维护。

1.1.3 汇编程序

用汇编语言编写的程序大大提高了程序的可读性，但失去了CPU能直接识别的特性。例如用汇编语言书写的指令：MOV AX, BX，CPU不会知道这几个字符所表达出来的功能，但程序员一看就知道：要求CPU把寄存器BX的值传送给寄存器AX。

把机器指令符号化增加了程序的可读性，但引起了如何让CPU知道程序员的用意，并按照其要求完成相应操作的问题。解决该问题就需要一个翻译程序，它能把汇编语言编写的源程序翻译成CPU能识别的机器指令序列。这里，我们称该翻译程序为汇编程序。

图1.1是汇编程序翻译过程的示意图。

把鼠标放到图上，将会看到动画效果，对此后的有关图片不再一一说明。

图1.1 汇编语言指令到机器指令的示意图

从图中不难看出：汇编程序能把左边汇编语言源程序翻译成右边的机器指令序列。其中，把汇编语言指令“MOV AX, BX”和“ADD AX, 5”分别转换成机器指令89D8H和050500H，而后者都是CPU能直接识别的，所以，可执行它们。

目前，常用的汇编程序有：MASM、TASM和DEBUG等。


1.1.4 汇编语言的特点

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 汇编语言的由来及其特点 → 主要特点

1.1.4 汇编语言的主要特点

一方面，汇编语言指令是用一些具有相应含义的助忆符来表达的，所以，它要比机器语言容易掌握和运用，但另一方面，它要直接使用CPU的资源，相对高级程序设计语言来说，它又显得难掌握。

汇编语言程序归纳起来大概有以下几个主要特性。

1、与机器相关性

汇编语言指令是机器指令的一种符号表示，而不同类型的CPU有不同的机器指令系统，也就有不同的汇编语言，所以，汇编语言程序与机器有着密切的关系。

由于汇编语言程序与机器的相关性，所以，除了同系列、不同型号CPU之间的汇编语言程序有一定程度的可移植性之外，其它不同类型(如：小型机和微机等)CPU之间的汇编语言程序是无法移植的，也就是说，汇编语言程序的通用性和可移植性要比高级语言程序低。

2、执行的高效率

正因为汇编语言有“与机器相关性”的特性，程序员用汇编语言编写程序时，可充分发挥自己的聪明才智，对机器内部的各种资源进行合理的安排，让它们始终处于最佳的使用状态，这样做的最终效果就是：程序的执行代码短，执行速度快。

现在，高级语言的编译程序在进行寄存器分配和目标代码生成时，也都有一定程度的优化(在后续课程《编译原理》的有关章节会有详细介绍)，但由于所使用的“优化策略”要适应各种不同的情况，所以，这些优化策略只能在宏观上，不可能在微观上、细节上进行优化。而用汇编语言编写程序几乎是程序员直接在写执行代码，程序员可以在程序的每个具体细节上进行优化，这也是汇编语言程序执行高效率的原因之一。

3、编写程序的复杂性

汇编语言是一种面向机器的语言，其汇编指令与机器指令基本上一一对应，所以，汇编指令也同机器指令一样具有功能单一、具体的特点。要想完成某件工作(如计算：A+B+C等)，就必须安排CPU的每步工作(如：先计算A+B，再把C加到前者的结果上)。另外，在编写汇编语言程序时，还要考虑机器资源的限制、汇编指令的细节和限制等等。

由于汇编语言程序要安排运算的每一个细节，这就使得编写汇编语言程序比较繁琐、复杂。一个简单的计算公式或计算方法，也要用一系列汇编指令一步一步来实现。

4、调试的复杂性

在通常情况下，调试汇编语言程序要比调试高级语言程序困难，其主要原因有四：
 | 汇编语言指令涉及到机器资源的细节，在调试过程中，要清楚每个资源的变化情况；
 | 程序员在编写汇编语言程序时，为了提高资源的利用率，可以使用各种实现技巧，而这些技巧完全有可能破坏程序的可读性。这样，在调试过程中，除了要知道每条指令的执行功能，还要清楚它在整个解题过程中的作用；
 | 高级语言程序几乎不显式地使用“转移语句”，但汇编语言程序要用到大量的、各类转移指令，这些跳转指令大大地增加了调试程序的难度。如果在汇编语言程序中也强调不使用“转移指令”，那么，汇编语言程序就会变成功能单调的顺序程序，这显然是不现实的；
 | 调试工具落后，高级语言程序可以在源程序级进行符号跟踪，而汇编语言程序只能跟踪机器指令。不过，现在这方面也有所改善，CV(CodeView)、TD(Turbo Debug)等软件也可在源程序级进行符号跟踪了。


1.1.5 汇编语言的应用领域

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 汇编语言的由来及其特点 → 使用领域

1.1.5 汇编语言的使用领域

综上所说，汇编语言的特点明显，其诱人的优点直接导致其严重的缺点，其“与机器相关”和“执行的高效率”导致其可移植性差和调试难。所以，我们在选用汇编语言时要根据实际的应用环境，尽可能避免其缺点对整个应用系统的影响。

下面简单列举几个领域以示说明，但不要把它们绝对化。

1、适用的领域

要求执行效率高、反应快的领域，如：操作系统内核，工业控制，实时系统等；

系统性能的瓶颈，或频繁被使用子程序或程序段；

与硬件资源密切相关的软件开发，如：设备驱动程序等；

受存储容量限制的应用领域，如：家用电器的计算机控制功能等；

没有适当的高级语言开发环境。

2、不宜使用的领域

大型软件的整体开发；

没有特殊要求的一般应用系统的开发等。


1.2 数据地表示和类型

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 数据的表示和类型

1.2 数据的表示和类型

在用汇编语言进行程序设计时，程序员可以直接访问内存，对数据在存储器内的表示形式要有一个清晰的认识。下面，我们只简单介绍本课程所要用到的数据表示知识，为后面的学习作一点必要的准备。

有关“数据表示”的详细内容请参阅《计算机组成原理》中的相关章节。

1.2.1 数值数据的表示

(1)、二进制

在计算机内，数值是用二进制来表示的，每个二进制数按权相加就可得到其十进制数值。在书写二进制时，为了区别，在数据后面紧跟一个字母B。

二进制的一般表现形式为：bn-1…b1b0B，其代表数值：bn-12n-1+…+b121+b020。

数据的二进制表示形式简单、明了，但它书写起来比较长，所以，通常情况下，我们在程序中不直接用二进制来书写具体的数值，而改用八进制、十进制或十六进制。

(2)、八进制

八进制是一种二进制的变形，三位二进制可变为一位八进制，反之也然。八进制的表示元素是：0、1、…、7。在书写时，为了区别，在数据后面紧跟一个字母Q。如：1234Q、7654Q、54Q等都是八进制。

八进制数在程序中的使用频率不高。

(3)、十进制

十进制是我们最熟悉的一种数据表示形式，它的基本元素是：0、1、…、9。在书写时，为了区别，在数据后面紧跟一个字母D。在程序中经常用十进制来表示数据。

(4)、十六进制

十六进制是另一种二进制的变形，四位二进制可变为一位十六进制，反之也然。十六进制的基本元素是：0、1、…、9、A、B、…、F(字母小写也可以)，其中：字母A、B、…、F依次代表10、11、…、15。

在书写时，为了区别，在数据后面紧跟一个字母H。当十六进制数的第一个字符是字母时，在第一个字符之前必须添加一个‘0’。如：100H、56EFH、0FFH、0ABCDH等都是十六进制数。

十六进制在程序中的使用频率很高。

(5)、数值进制的总结和相互转换

表1.1 各种进制及其字符表示

进制 | 字符 | 例子 | 备注
二进制 | B/Y(*) | 1010B、1011B | (*)：字符Y、O和T是宏汇编MASM系统所增加的进制表示符。
八进制 | Q/O | 1234Q、311Q
十进制 | D/T | 1234D、512D
十六进制 | H | 1234H、1011H

下面是各进制数据之间进行转换的控件，浏览者通过它可很好地掌握这些进制之间的转换方法。当十进制转化为其它进制时，浏览者还可进行实际的练习操作。


1.2.1 数值表示及其扩展

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 数据的表示和类型 → 数的表示

(6)、数的补码表示法

在计算机内，为了表示正负数，并便于进行各种算术运算，对有符号数采用二进制的补码表示形式。

补码的最高位用来表示正负数：0—正数，1—负数。

正数的补码是其自身的二进制形式，负数的补码是把其正数的二进制编码变“反”，再加1而得。

(7)、二进制数的符号扩展

在汇编语言中，我们经常要对字/字节的数据进行操作。当把“字节”转换成“字”，或“字”转换成“双字”时，就需要进行符号扩展。符号扩展的具体操作就是把已知信息的最高位扩展到所有更高位。

例1.1 把8位补码01011010、10101100分别扩展成16位补码。
解：根据符号扩展的含义，“字节→字”的具体扩展结果如下：

 | 01011010 |  | 10101100
00000000 | 01011010 | 11111111 | 10101100

例1.2 把16位补码0101101111001010、1010111101011011别扩展成32位补码。
解：根据符号扩展的含义，“字→双字”的具体扩展结果如下：

 | 0101101111001010 |  | 1010111101011011
0000000000000000 | 0101101111001010 | 1111111111111111 | 1010111101011011

(8)、n位二进制的表示范围

n位二进制所能表示的无符号整数的范围：0≤x≤2n-1。
    n位二进制所能表示的有符号整数(补码表示)的范围：-2n-1≤x≤2n-1-1。

在汇编语言中，常用到n为8和16时的数值范围：

n=8时，无符号整数的范围：0~255，有符号整数的范围：-128~127；
     n=16时，无符号整数的范围：0~65535，有符号整数的范围：-32768~32767。

(9)、BCD码

通常，我们习惯用十进制表示的数据，但计算机是用二进制来表示数数据的，这就需要进行数值进制之间的转换。我们把每位十进制数转换二进制的编码，简称为BCD码(Binary Coded Decimal)。

BCD码是用4位二进制编码来表示1位十进制数。这种编码方法有多种，但常用的编码是8421BCD编码，如表1.2所示。这种BCD编码实际上就是0~9的“等值”二进制数。

表1.2 8421BCD编码列表

十进制数字 | 8421BCD码 | 十进制数字 | 8421BCD码
0 | 0000 | 5 | 0101
1 | 0001 | 6 | 0110
2 | 0010 | 7 | 0111
3 | 0011 | 8 | 1000
4 | 0100 | 9 | 1001

用BCD码进行进制的转换时，是要求在二种进制的表现形式上快速转换，而不是要求在“数值相等”的含义快速转换。

例1.3 求十进制数2000的BCD编码和其二进制数。

解：2000的BCD编码是把每位上的数2、0、0、0分别转换为其对应的BCD编码：0010、0000、0000和0000，把它们合在一起就是2000的BCD编码：0010 0000 0000 0000。

十进制数2000的二进制数是：11111010000，它们在数值上是相等的。


1.2.2 非数值数据的表示

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 数据的表示和类型 → 非数值数据的表示

1.2.2 非数值数据的表示

计算机除了具有进行数值计算能力之外，还具有进行非数值计算的能力。现在，后者的应用领域已远远超过了前者的应用领域，如：文字处理、图形图象处理、信息检索、日常的办公管理等。所以，对非数值信息的编码就显得越加重要。

1、ASCII码

ASCII码(American Standard Code for Information Interchange)是目前应用极其广泛的一种信息编码，许多计算机系统都是采用它为字符进行编码。它是一种7位二进制编码。

右表是ASCII码的具体编码方案。在该表中，对学习本课程有用的主要信息有：

字符'0'~'9'是连续编码的，其编码的低4位就是该字符在十进制中的数值；

小写字母的编码比大写字母的编码大，对应字母的编码之间相差20H。

当然，从ASCII码表中还可看出其它有用信息，还有扩展的ASCII码等知识，但这些内容对学习本课程的帮助不明显，故不再叙述。有兴趣的读者可参阅其它书籍。

表1.3 ASCII码的编码方案

高位 低位 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111
0000 | NUL | DEL | SP | 0 | @ | P | ` | p
0001 | SOH | DC1 | ! | 1 | A | Q | a | q
0010 | STX | DC2 | “ | 2 | B | R | b | r
0011 | ETX | DC3 | # | 3 | C | S | c | s
0100 | EOT | DC4 | $ | 4 | D | T | d | t
0101 | ENQ | NAK | % | 5 | E | U | e | u
0110 | ACK | SYN | & | 6 | F | V | f | v
0111 | BEL | ETB | ‘ | 7 | G | W | g | w
1000 | BS | CAN | ( | 8 | H | X | h | x
1001 | HT | EM | ) | 9 | I | Y | i | y
1010 | LF | SUB | * | : | J | Z | j | z
1011 | VT | ESC | + | ; | K | [ | k | {
1100 | FF | FS |  | < | L | \ | l | |
1101 | CR | GS | - | = | M | ] | m | }
1110 | SO | RS | . | > | N | ^ | n | ~
1111 | SI | US | / | ? | O | _ | o | Del

2、汉字编码

ASCII码是针对英文的字母、数字和其它特殊字符进行编码的，它不能用于对汉字的编码。要想用计算机来处理汉字，就必须先对汉字进行适当的编码。我国在1981年5月对6000多个常用的汉字制定了交换码的国家标准，即：GB2312-80。该标准规定了汉字交换用的基本汉字字符和一些图形字符，它们共计7445个，其中汉字有6763个。该标准给定每个字符的二进制编码，即国标码。

有关汉字编码的详细信息，请参阅其它有关书籍，在此不再介绍。


1.2.3 基本的数据类型

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 数据的表示和类型 → 基本数据类型

1.2.3 基本的数据类型

汇编语言所用到的基本数据类型为：字节、字、双字等，这些数据类型在以后的章节中都有相应的类型说明符。下面对它们进行最基本的描述。

1、字节

一个字节有8位二进制组成，其最高位是第7位，最低位是第0位，如右图所示。在表示有符号数时，最高位就是符号位。

通常情况下，存储器按字节编址，读写存储器的最小信息单位就是一个字节。

图1.2 字节数据类型示意图

2、字

由2个字节组成一个字，其最高位是第15位，最低位是第0位。高8位称为高字节，低8位称为低字节，如右图所示。

字节和字是汇编语言程序中最常用的两种数据类型，也是最容易出错的数据类型。

图1.3 字数据类型示意图

3、双字

用2个字(4个字节)来组成一个双字，其高16位称为高字，低16位称为低字，如右图所示。

双字有较大的数据表示范围，它通常是为了满足数据的表示范围而选用的数据类型，也可用于存储远指针。

字节、字和双字是汇编语言最常用的三种数据类型，下图表现出它们三者之间的组成关系。

图1.4 双字数据类型示意图

图1.5 字节、字和双字数据类型之间关系示意图

4、四字

由4个字(8个字节)组成一个四字类型，它总共有64个二进制位，当然，也就有更大的数据表示范围，但在汇编语言中很少使用该数据类型。

5、十字节

由10个字节组成一个十字节类型，它总共有80个二进制位。在汇编语言中很少使用该数据类型。

6、字符串

字符串是由若干个字节组成的，字节数不定，通常每个字节存储一个字符。该数据形式是汇编语言程序中经常使用的另一种数据形式。


1.3 习题

1 机器语言
1 机器语言
2 汇编语言
2 汇编语言
3 汇编程序
3 汇编程序
4 汇编语言的主要特点
4 汇编语言的主要特点
5 汇编语言的使用领域
5 汇编语言的使用领域
1 数值数据的表示
1 数值数据的表示
2 非数值数据的表示
2 非数值数据的表示
3 基本的数据类型
3 基本的数据类型
本章的学习内容和要求
本章的学习内容和要求
1.1 汇编语言的由来及其特点
1.1 汇编语言的由来及其特点
1.2 数据的表示和类型
1.2 数据的表示和类型
1.3 习题
1.3 习题

→ 习题

1.3 习题

1.1、汇编语言的主要特点有哪些？

1.2、汇编语言适用于哪些领域，在哪些领域使用不太方便？

1.3、在汇编语言中，如何表示二进制、八进制、十进制和十六进制的数值？

1.4、在计算机中，如何表示正、负数？在保持数值大小不变的情况下，如何把位数少的二进制数值扩展成位数较多的二进制数值？

1.5、在ASCII表，字符'0'~'9'与数值0~9之间编码规律是什么？大写字母和小写字母之间的编码规律是什么？

1.6、汇编语言中的基本数据类型有哪些？它与高级程序设计语言(如：C语言)中的数据类型的对应关系是什么？


第2章 CPU资源和存储器

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

.kmas;dlf

第2章 CPU资源和存储器

计算机的硬件资源是用汇编语言编程所必须要了解的重要内容，因为汇编语言允许、也需要程序员直接使用这些硬件资源，只有这样才能编写出高效的目标代码。

在汇编语言中，需要访问的硬件资源主要有：CPU内部资源、存储器和I/O端口。本章将着重讲解CPU内部寄存器的命名、功能及其常见的用途，还要介绍存储器的分段管理模式、存储单元地址的表示法以及其物理地址的形成方式。

2.1 寄存器组

寄存器是CPU内部重要的数据存储资源，是汇编程序员能直接使用的硬件资源之一。由于寄存器的存取速度比内存快，所以，在用汇编语言编写程序时，要尽可能充分利用寄存器的存储功能。

寄存器一般用来保存程序的中间结果，为随后的指令快速提供操作数，从而避免把中间结果存入内存，再读取内存的操作。在高级语言(如：C/C++语言)中，也有定义变量为寄存器类型的，这就是提高寄存器利用率的一种可行的方法。

另外，由于寄存器的个数和容量都有限，不可能把所有中间结果都存储在寄存器中，所以，要对寄存器进行适当的调度。根据指令的要求，如何安排适当的寄存器，避免操作数过多的传送操作是一项细致而又周密的工作。有关“寄存器的分配策略”在后续课程《编译原理》中会有详细的介绍。

由于16位/32位CPU是微机CPU的两个重要代表，所以，在此只介绍它们内部寄存器的名称及其主要功能。

2.1.1 寄存器组

1、 16位寄存器组

16位CPU所含有的寄存器有(见图2.1中16位寄存器部分)：
4个数据寄存器(AX、BX、CX和DX) | 2个变址和指针寄存器(SI和DI) | 2个指针寄存器(SP和BP)
4个段寄存器(ES、CS、SS和DS) | 1个指令指针寄存器(IP) | 1个标志寄存器(Flags)

2、 32位寄存器组

32位CPU除了包含了先前CPU的所有寄存器，并把通用寄存器、指令指针和标志寄存器从16位扩充成32位之外，还增加了2个16位的段寄存器：FS和GS。

32位CPU所含有的寄存器有(见图2.1中的寄存器)：
4个数据寄存器(EAX、EBX、ECX和EDX) | 2个变址和指针寄存器(ESI和EDI) | 2个指针寄存器(ESP和EBP)
6个段寄存器(ES、CS、SS、DS、FS和GS) | 1个指令指针寄存器(EIP) | 1个标志寄存器(EFlags)

图2.1 CPU寄存器组的示意图


2.1.1 存储器组

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 寄存器组 → 通用寄存器的作用

2.1.2、通用寄存器的作用

通用寄存器可用于传送和暂存数据，也可参与算术逻辑运算，并保存运算结果。除此之外，它们还各自具有一些特殊功能。汇编语言程序员必须熟悉每个寄存器的一般用途和特殊用途，只有这样，才能在程序中做到正确、合理地使用它们。

表2.1 通用寄存器的主要用途

寄存器的分类 |  | 寄存器 | 主 要 用 途
通 用 寄 存 器 | 数据 寄存器 | AX | 乘、除运算，字的输入输出，中间结果的缓存
 |  | AL | 字节的乘、除运算，字节的输入输出，十进制算术运算
 |  | AH | 字节的乘、除运算，存放中断的功能号
 |  | BX | 存储器指针
 |  | CX | 串操作、循环控制的计数器
 |  | CL | 移位操作的计数器
 |  | DX | 字的乘、除运算，间接的输入输出
 | 变址 寄存器 | SI | 存储器指针、串指令中的源操作数指针
 |  | DI | 存储器指针、串指令中的目的操作数指针
 | 变址 寄存器 | BP | 存储器指针、存取堆栈的指针
 |  | SP | 堆栈的栈顶指针
指令指针 |  | IP/EIP |
标志位寄存器 |  | Flag/EFlag |
32位 CPU的 段寄存器 | 16位CPU的 段寄存器 | ES | 附加段寄存器
 |  | CS | 代码段寄存器
 |  | SS | 堆栈段寄存器
 |  | DS | 数据段寄存器
 | 新增加的 段寄存器 | FS | 附加段寄存器
 |  | GS | 附加段寄存器

更详细的内容请点击：通用寄存器功能的说明。


常用寄存器功能说明

此网页使用了框架，但您的浏览器不支持框架。


16/32位标志位

此网页使用了框架，但您的浏览器不支持框架。


2.1.2 程序状态字

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 寄存器组 → 专用寄存器的作用

2.1.3、专用寄存器的作用

16位CPU内部有一个16位的标志寄存器，它包含9个标志位。这些标志位主要用来反映处理器的状态和运算结果的某些特征。各标志位在标志寄存器内的分布如图2.2所示。

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF 　 AF 　 PF 　 CF
31 … 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
… … VM RF NT IOPL OF DF IF TF SF ZF 　 AF PF CF

图2.2 16位/32位标志寄存器的示意图

上面9个标志位可分为二组：运算结果标志位(有背景色的标志位)和状态控制标志位。前者受算术运算和逻辑运算结果的影响，后者受一些控制指令执行的影响。

更详细的内容请点击：标志位的说明。

有些指令的执行会改变标志位(如：算术运算指令等)，不同的指令会影响不同的标志位，有些指令的执行不改变任何标志位(如：MOV指令等)，有些指令的执行会受标志位的影响(如：条件转移指令等)，也有指令的执行不受其影响。

程序员要想熟练运用这些标志位，就必须掌握每个标志位的含义、每条指令的执行条件和执行结果对标志位的作用。

注意：虽然知道每个标志位在标志寄存器内的具体位置是有好处的，但通常情况下，没有这个必要。在使用第5.2.9节中的“条件转移指令”时，系统会自动引用相应标志位的值来决定是否需要“转移”的，所以，不必过分强调标志位在标志寄存器内的具体位置。


2.2 16位微机存储器的管理模式

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 存储器的管理模式

2.2 存储器的管理模式

Intel公司的80X86系列的CPU基本上采用内存分段的管理模式。它把内存和程序分成若干个段，每个段的起点用一个段寄存器来记忆，所以，学习微机汇编语言，必须要清楚地理解存储器的分段含义、存储单元的逻辑地址和其物理地址之间的转换关系。

2.2.1 16位微机的内存管理模式

1、存储器的分段

我们知道：计算机的内存单元是以“字节”为最小单位进行线性编址的。为了标识每个存储单元，就给每个存储单元规定一个编号，此编号就是该存储单元的物理地址。

存储单元的物理地址是一个无符号的二进制数。但为了书写的简化，物理地址通常用十六进制来表示。

16位CPU内部有20根地址线，其编码区间为：00000H~0FFFFFH，所以，它可直接访问的物理空间为1M(220)字节。而16位CPU内部存放存储单元偏移量的寄存器（如：IP、SP、BP、SI、DI和BX等）都是16位，它们的编码范围仅为：00000H~0FFFFH。这样，如果用16位寄存器来访问内存的话，则只能访问内存的最低端的64K，其它的内存将无法访问。为了能用16位寄存器来有效地访问1M的存储空间，16位CPU采用了内存分段的管理模式，并引用段寄存器的概念。

16位微机把内存空间划分成若干个逻辑段，每个逻辑段的要求如下： |  | 图2.4 16位微机内存分段管理示意图
 | 逻辑段的起始地址(通常简称为：段地址)必须是16的倍数，即最低4位二进制必须全为0；
 | 逻辑段的最大容量为64K，这由16位寄存器的寻址空间所决定。
按上述规定，1M内存最多可分成64K个段，即65536个段(段之间相互重叠)，至少可分成16个相互不重叠的段。 右图2.4是内存各逻辑段之间的分布情况示意图，其中有相连的段(如：C和D段)、不相连的段(如：A和B段)以及相互重叠的段(如：B和C段)。

这种存储器分段的内存管理方法不仅实现了用两个16位寄存器来访问1M的内存空间，而且对程序的重定位、浮动地址的编码和提高内存的利用率等方面都具有重要的实用价值。


2.2.1 物理地址的形成方式

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 存储器的管理模式 → 物理地址的形成方式

2、物理地址的形成方式

由于规定段地址必须是16的倍数，所以，其值的一般形式为：XXXX0H，即：前16位二进制位是变化的，后四位是固定为0。鉴于段地址的这种特性，我们可以仅保存其前16位二进制来达到保存整个段地址，其后四位可通过“左移补0”来获得。

在确定了某个存储单元所属的内存段后，我们也只知道其所处内存位置的范围，还不能确定其具体位置。要想确定内存单元的具体位置，还必须知道该单元离该段地址有多远。我们通常把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移，也可称为有效地址(EA—Effective Address)或偏移量(Offset)等。有了段地址和偏移量，就能唯一地确定某一内存单元在存储器内的具体位置。

由此可见，存储单元的逻辑地址分为两部分：段地址和偏移量。由逻辑地址得到其物理地址(PA—Physical Address)的计算方法如下：

物理地址PA=段地址×16 + 偏移量

计算存储单元物理地址的公式可用“左移4位”和“加”运算来实现。图2.5是物理地址的计算示意图。

图2.5 16位CPU物理地址计算示意图 | 图2.6 物理地址和逻辑地址之间的关系

对物理地址来说，当段地址变化时，只要对其偏移量进行相应的调整就可对应同一个物理地址，所以，同一个物理地址可有多个逻辑地址。如图2.6所示。

在汇编语言程序中，存储单元通常不是用其物理地址标识的，而是用其逻辑地址标识的。逻辑地址的段地址由段寄存器给出，偏移量可由寄存器(SI、DI、BP和BX等)给出，也可用符号地址或具体的数值给出。至于在指令中如何指出存储单元的逻辑地址将在第3章“寻址方式”中给出详细说明。


2.2.1 段寄存器的引用

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 存储器的管理模式 → 段寄存器的引用

3、段寄存器的引用

段寄存器是因为对内存的分段管理而设置的。16位CPU有四个段寄存器，所以，其程序可同时访问四个不同含义的段。段寄存器及其偏移量的引用关系如图2.7所示。

段寄存器CS指向存放程序的内存段，IP是用来存放下条待执行的指令在该段的偏移量，把它们合在一起可在该内存段内取到下次要执行的指令。

段寄存器SS指向用于堆栈的内存段，SP是用来指向该堆栈的栈顶，把它们合在一起可访问栈顶单元。另外，当偏移量用到了指针寄存器BP，则其缺省的段寄存器也是SS，并且用BP可访问整个堆栈，不仅仅是只访问栈顶。

段寄存器DS指向数据段，ES指向附加段，在存取操作数时，二者之一和一个偏移量合并就可得到存储单元的物理地址。该偏移量可以是具体数值、符号地址和指针寄存器的值等之一，具体情况将由指令的寻址方式来决定。

图2.7 段和段寄存器的引用示意图

通常，缺省的数据段寄存器是DS，只有一个例外，即：在进行串操作时，其目的地址的段寄存器规定为ES。当然，在一般指令中，我们还可以用强置前缀的方法来改变操作数的段寄存器(见：第3.3节中的强置前缀的书写格式)。

一般情况下，段寄存器及其指针寄存器的引用关系如下表所示。表2.2中的“可选用的段寄存器”即是可以用强置说明这些段寄存器的值来作为其操作数地址的段地址。

表2.2 段寄存器及其指针寄存器的引用关系

访问存储器方式 |  | 缺省的段寄存器 | 可选用的段寄存器 | 偏移量
取指令 |  | CS |  | IP
堆栈操作 |  | SS |  | SP
一般取操作数 |  | DS | CS、ES、SS | 有效地址
串操作 | 源操作数 | DS | CS、ES、SS | SI
 | 目标操作数 | ES |  | DI
使用指针寄存器BP |  | SS | CS、DS、ES | 有效地址

由上表可以看出16位CPU在段寄存器的引用方面有如下规定：

、取指令所用的段寄存器和偏移量一定是用CS和IP；

、堆栈操作所用的段寄存器和偏移量一定是SS和SP；

、串操作的目标操作数所用的段寄存器和偏移量一定是ES和DI；

、其它情况，段寄存器除了其默认引用的寄存器外，还可以强行改变为其它段寄存器。

对于上述规定，随着后续内容的叙述，将会对它们有更进一步理解。


2.2.1 存储单元的内容

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 存储器的管理模式 → 存储单元的内容

4、存储单元的内容

上面，我们讲述了16位微机的内存管理及其相关知识，知道了内存单元物理地址的计算方法，这使我们能很容易地指定所要访问的存储单元。但存储单元里的内容是如何存放的呢？下面就能描述数值在内存的存放形式。

存储单元中所存放的二进制信息通常称为该存储单元的内容或值，并且规定：

、一个字节的内容是该字节单元内存放的二进制信息；

、一个字的内容是该字地址所指向的单元及其后继一个单元的内容拼接而成；

、一个双字的内容是该字地址所指向的单元及其后继三个单元的内容拼接而成。

在拼接“字内容”时，我们按“高高低低”的原则来处理，即：高存储单元(地址大的存储单元)的值是“字内容”的高8位，低存储单元(地址小的存储单元)的值是“字内容”的低8位。在拼接“双字内容”时也是如此。

右图2.8是一段内存单元存放数据的例子。

从图中可看出下列存储结果：

图2.8 内存单元存放内容示意图

、字节12340H、12341H的内容分别为：12H和34H等；

、字12340H、12341H的内容分别为：3412H和5634H等；

、双字12340H、12341H的内容分别为：78563412H和90785634H等。


2.2.2 32位微机存储器的管理模式

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 存储器的管理模式 → 32位内存管理模式

2.2.2 32位微机的内存管理模式

32位微机的内存存管理仍然采用“分段”的管理模式，存储器的逻辑地址同样由段地址和偏移量两部分组成。32位微机的内存管理与16位微机的有相同之处，也有不同之处，因为它提供了两种不同工作方式：实方式和保护方式。

1、物理地址的计算方式

实方式：段地址仍然是16的倍数，每个段的最大容量仍为64K。段寄存器的值是段的起始地址，存储单元的物理地址仍为段寄存器的值乘16，再加上段内偏移量。在此方式下，32位微机的内存管理与16位微机是相一致的。

保护方式：段地址可以长达32位，其值可以不是16的倍数，每个段的最大容量可达4G。段寄存器的值是表示段地址的“选择器”(Selector)，用该“选择器”可从内存中得到一个32位的段地址，存储单元的物理地址就是该段地址加上段内偏移量，这与16位微机的物理地址计算完全不同。

2、段寄存器的引用

32位CPU内有6个段寄存器，程序在某一时刻可访问6个不同的段。其段寄存器的值在不同的方式下具有不同的含义：

(1)、在实方式下，段寄存器的值就是段地址；

(2)、在保护方式下，段寄存器的值不是段地址，是段地址的“选择器”。它间接指出一个32位的段地址。

下面分别说明各段寄存器的用法和作用。

代码段寄存器：32位微机在取指令时，系统自动引用CS和EIP来取出下条指令。在实方式下，由于段的最大容量不超过64K，所以，EIP的高16位全为0，其效果相当于16位CPU中的IP。

堆栈段寄存器：32位微机在访问堆栈段时，总是引用堆栈段寄存器SS。但在不同的方式下其堆栈指针有所不同：

1)、在实方式下，32位微机把ESP的低16位SP作为指向堆栈的指针，所以，我们可以认为栈顶单元是由SS和SP来指定的。这就与16位微机访问栈顶单元的方法相一致；

2)、在保护方式下，堆栈指针可用32位的ESP和16位的SP。

数据段寄存器：DS是主要的数据段寄存器。通常情况下，它是除访问堆栈以外数据时的默认段寄存器。在某些串操作中，其目的操作数的段寄存器被指定为ES是另一个例外。

另外，段寄存器CS、SS、ES、FS和GS也都可以作为访问数据时的段寄存器，但它们必须用段超越前缀的方式在指令中直接写出。用这种方式会增加指令的长度，指令的执行时间也有所延长。

一般来说，程序频繁访问的数据段用DS来指向，不太经常访问的数据段可用ES、FS和GS等来指向。

3、存储单元的内容

32位微机存储单元内容的存储格式与16位微机的完全一致，也都采用“高高低低”的原则来存放数据。


2.3 习题

1 寄存器组
1 寄存器组
2 通用寄存器的作用
2 通用寄存器的作用
3 专用寄存器的作用
3 专用寄存器的作用
1 16位微机的内存管理模式
1 16位微机的内存管理模式
2 32位微机的内存管理模式
2 32位微机的内存管理模式
本章的学习内容和要求
本章的学习内容和要求
2.1 寄存器组
2.1 寄存器组
2.2 存储器的管理模式
2.2 存储器的管理模式
2.3 习题
2.3 习题

→ 习题

2.3 习题

2.1、在16位CPU中，有哪些8位寄存器、16位寄存器？哪些16位寄存器可分为二个8位寄存器来使用？

2.2、和16位CPU相比，在32位CPU中，扩展了哪些寄存器？哪些寄存器的位数没变？所增加的寄存器是什么？

2.3、简述各通用寄存器的主要功能？

2.4、简述各段寄存器所指段的含义。

2.5、IP／EIP寄存器的用途是什么？它是《计算机组成原理》中的什么概念在CPU中的具体体现？

2.6、在标志寄存器中，用于反映运算结果属性的标志位有哪些？它们每一位所表示的含义是什么？在Debug环境下，用什么符号来表示之？

2.7、在标志寄存器中，用于反映CPU状态控制的标志位有哪些？它们每一位所表示的含义是什么？在Debug环境下，用什么符号来表示之？

2.8、填空

1)、16位/32位CPU在取指令时，分别需要用到的寄存器有 和 、 和 ；

2)、访问堆栈段栈顶单元要由 段寄存器和 寄存器组合来寻址，访问堆栈段中的单元要由 段寄存器和 寄存器组合来寻址；

3)、用基指针(BP)寻址存储器，则其用到的段寄存器是 ；

4)、访问数据段中的单元要由 段寄存器和 寄存器组合来寻址；

5)、在16位/32位微机系统中，程序最多能同时使用 数据段和 数据段；

2.9、假设用以下寄存器组合来访问存储单元，试求出它们所访问单元的物理地址。

1)、DS=1000H和DI=2000H
3)、SS=2300H和BP=3200H
5)、SS=2900H和SP=3A00H

 2)、DS=2000H和SI=1002H
4)、DS=A000H和BX=1000H

2.10、在实方式下，一个20位的物理地址最多可有 个段值和偏移量来表示。

2.11、在默认情况下，哪些寄存器可用于读取堆栈段的数据?

2.12、按组画出下列数值在内存中的存储形式(假设同组分配在连续存储单元中)。

1)、字节值：1、2、'1'、'A'、'a'和0；
2)、字值：12H、6502H、-1和0；
3)、双字值：-765H、12345678H和0；


3.1 立即数寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

第3章 操作数的寻址方式

操作数是指令或程序的主要处理对象。如果某条指令或某个程序不处理任何操作数，那么，该指令或程序不可能有数据处理功能。在CPU的指令系统中，除NOP(空操作指令)、HLT(停机指令)等少数指令之外，大量的指令在执行过程中都会涉及到操作数。所以，在指令中如何表达操作数或操作数所在位置就是正确运用汇编指令的一个重要因素。

在指令中，指定操作数或操作数存放位置的方法称为寻址方式。操作数的各种寻址方式是用汇编语言进行程序设计的基础，也是本课程学习的重点之一。

微机系统有七种基本的寻址方式：立即寻址方式、寄存器寻址方式、直接寻址方式、寄存器间接寻址方式、寄存器相对寻址方式、基址加变址寻址方式、相对基址加变址寻址方式等。其中，后五种寻址方式是确定内存单元有效地址的五种不同的计算方法，用它们可方便地实现对数组元素的访问。

另外，在32位微机系统中，为了扩大对存储单元的寻址能力，增加了一种新的寻址方式——32位地址的寻址方式。

为了表达方便，我们用符号“(X)”表示X的值，如：(AX)表示寄存器AX的值。

3.1 立即寻址方式

操作数作为指令的一部分而直接写在指令中，这种操作数称为立即数，这种寻址方式也就称为立即数寻址方式。

立即数可以是8位、16位或32位，该数值紧跟在操作码之后。如果立即数为16位或32位，那么，它将按“高高低低”的原则进行存储。例如：

MOV AH, 80H　　　ADD AX, 1234H　　　MOV ECX, 123456H
MOV B1, 12H　　　MOV W1, 3456H　　 ADD D1, 32123456H

其中：B1、W1和D1分别是字节、字和双字单元。

以上指令中的第二操作数都是立即数，在汇编语言中，规定：立即数不能作为指令中的第二操作数。该规定与高级语言中“赋值语句的左边不能是常量”的规定相一致。

立即数寻址方式通常用于对通用寄存器或内存单元赋初值。图3.1是指令“MOV AX, 4576H”存储形式和执行示意图。

图3.1 立即寻址方式的存储和执行示意图


3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 寄存器寻址方式

3.2 寄存器寻址方式

指令所要的操作数已存储在某寄存器中，或把目标操作数存入寄存器。把在指令中指出所使用寄存器(即：寄存器的助忆符)的寻址方式称为寄存器寻址方式。

指令中可以引用的寄存器及其符号名称如下：

、8位寄存器有：AH、AL、BH、BL、CH、CL、DH和DL等；
、16位寄存器有：AX、BX、CX、DX、SI、DI、SP、BP和段寄存器等；
、32位寄存器有：EAX、EBX、ECX、EDX、ESI、EDI、ESP和EBP等。

寄存器寻址方式是一种简单快捷的寻址方式，源和目的操作数都可以是寄存器。

1、源操作数是寄存器寻址方式

如：ADD VARD, EAX　　ADD VARW, AX　　　MOV VARB, BH等。

其中：VARD、VARW和VARB是双字，字和字节类型的内存变量。在第4章将会学到如何定义它们。

2、目的操作数是寄存器寻址方式

如：ADD BH, 78h　　　　ADD AX, 1234h　　　MOV EBX, 12345678H等。

3、源和目的操作数都是寄存器寻址方式

如：MOV EAX, EBX　　　MOV AX, BX　　　　 MOV DH, BL等。

由于指令所需的操作数已存储在寄存器中，或操作的结果存入寄存器，这样，在指令执行过程中，会减少读/写存储器单元的次数，所以，使用寄存器寻址方式的指令具有较快的执行速度。通常情况下，我们提倡在编写汇编语言程序时，应尽可能地使用寄存器寻址方式，但也不要把它绝对化。


3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 直接寻址方式

3.3 直接寻址方式

指令所要的操作数存放在内存中，在指令中直接给出该操作数的有效地址，这种寻址方式为直接寻址方式。

在通常情况下，操作数存放在数据段中，所以，其物理地址将由数据段寄存器DS和指令中给出的有效地址直接形成，但如果使用段超越前缀，那么，操作数可存放在其它段。

例3.1 假设有指令：MOV BX, [1234H]，在执行时，(DS)=2000H，内存单元21234H的值为5213H。问该指令执行后，BX的值是什么？

解：根据直接寻址方式的寻址规则，把该指令的具体执行过程用图3.2来表示。

从图3.2中，可看出执行该指令要分三部分：

图3.2 直接寻址方式的存储和执行示意图

、由于1234H是一个直接地址，它紧跟在指令的操作码之后，随取指令而被读出；

、访问数据段的段寄存器是DS，所以，用DS的值和偏移量1234H相加，得存储单元的物理地址：21234H；

、取单元21234H的值5213H，并按“高高低低”的原则存入寄存器BX中。

所以，在执行该指令后，BX的值就为5213H。

由于数据段的段寄存器默认为DS，如果要指定访问其它段内的数据，可在指令中用段前缀的方式显式地书写出来。

下面指令的目标操作数就是带有段前缀的直接寻址方式。

MOV　ES:[1000H], AX

直接寻址方式常用于处理内存单元的数据，其操作数是内存变量的值，该寻址方式可在64K字节的段内进行寻址。

注意：立即寻址方式和直接寻址方式的书写格式的不同，直接寻址的地址要写在括号“[”，“]”内。在程序中，直接地址通常用内存变量名来表示，如：MOV BX, VARW，其中，VARW是内存字变量。

试比较下列指令中源操作数的寻址方式(VARW是内存字变量)：

MOV AX, 1234H | MOV AX, [1234H] | ;前者是立即寻址，后者是直接寻址
MOV AX, VARW | MOV AX, [VARW] | ;两者是等效的，均为直接寻址


3.4 寄存器间接寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 寄存器间接寻址方式

3.4 寄存器间接寻址方式

操作数在存储器中，操作数的有效地址用SI、DI、BX和BP等四个寄存器之一来指定，称这种寻址方式为寄存器间接寻址方式。该寻址方式物理地址的计算方法如下：

寄存器间接寻址方式读取存储单元的原理如图3.3所示。

图3.3 读取操作数过程的示意图

在不使用段超越前缀的情况下，有下列规定：

若有效地址用SI、DI和BX等之一来指定，则其缺省的段寄存器为DS；
若有效地址用BP来指定，则其缺省的段寄存器为SS(即：堆栈段)。

例3.2 假设有指令：MOV BX,[DI]，在执行时，(DS)=1000H，(DI)=2345H，存储单元12345H的内容是4354H。问执行指令后，BX的值是什么？

解：根据寄存器间接寻址方式的规则，在执行本例指令时，寄存器DI的值不是操作数，而是操作数的地址。该操作数的物理地址应由DS和DI的值形成，即：

PA=(DS)*16+DI=1000H*16+2345H=12345H。

所以，该指令的执行效果是：把从物理地址为12345H开始的一个字的值传送给BX。

其执行过程如右图3.4所示。

图3.4 读取操作数过程的示意图


3.5 寄存器相对寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 寄存器相对寻址方式

3.5 寄存器相对寻址方式

操作数在存储器中，其有效地址是一个基址寄存器(BX、BP)或变址寄存器(SI、DI)的内容和指令中的8位/16位偏移量之和。其有效地址的计算公式如右式所示。

在不使用段超越前缀的情况下，有下列规定：

、若有效地址用SI、DI和BX等之一来指定，则其缺省的段寄存器为DS；

、若有效地址用BP来指定，则其缺省的段寄存器为SS。

指令中给出的8位/16位偏移量用补码表示。在计算有效地址时，如果偏移量是8位，则进行符号扩展成16位。当所得的有效地址超过0FFFFH，则取其64K的模。

例3.3 假设指令：MOV BX, [SI+100H]，在执行它时，(DS)=1000H，(SI)=2345H，内存单元12445H的内容为2715H，问该指令执行后，BX的值是什么？

解：根据寄存器相对寻址方式的规则，在执行本例指令时，源操作数的有效地址EA为：

图3.5 寄存器相对寻址方式的执行过程示意图

EA=(SI)+100H=2345H+100H=2445H

该操作数的物理地址应由DS和EA的值形成，即：

PA=(DS)*16+EA=1000H*16+2445H=12445H。

所以，该指令的执行效果是：把从物理地址为12445H开始的一个字的值传送给BX。

其执行过程如图3.5所示。


3.6 基址加变址寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 基址加变址寻址方式

3.6 基址加变址寻址方式

操作数在存储器中，其有效地址是一个基址寄存器(BX、BP)和一个变址寄存器(SI、DI)的内容之和。其有效地址的计算公式如右式所示。

在不使用段超越前缀的情况下，规定：如果有效地址中含有BP，则缺省的段寄存器为SS；否则，缺省的段寄存器为DS。

例3.4 假设指令：MOV BX, [BX+SI]，在执行时，(DS)=1000H，(BX)=2100H，(SI)=0011H，内存单元12111H的内容为1234H。问该指令执行后，BX的值是什么？

解：根据基址加变址寻址方式的规则，在执行本例指令时，源操作数的有效地址EA为：

EA=(BX)+(SI)=2100H+0011H=2111H

该操作数的物理地址应由DS和EA的值形成，即：

PA=(DS)*16+EA=1000H*16+2111H=12111H

所以，该指令的执行效果是：把从物理地址为12111H开始的一个字的值传送给BX。

其执行过程如右图3.6所示。

图3.6 基址加变址寻址方式的执行过程示意图


3.7 相对基址加变址寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 相对基址加变址寻址方式

3.7 相对基址加变址寻址方式

操作数在存储器中，其有效地址是一个基址寄存器(BX、BP)的值、一个变址寄存器(SI、DI)的值和指令中的8位/16位偏移量之和。其有效地址的计算公式如右式所示。

在不使用段超越前缀的情况下，规定：如果有效地址中含有BP，则其缺省的段寄存器为SS；否则，其缺省的段寄存器为DS。

指令中给出的8位/16位偏移量用补码表示。在计算有效地址时，如果偏移量是8位，则进行符号扩展成16位。当所得的有效地址超过0FFFFH，则取其64K的模。

例3.5 假设指令：MOV AX, [BX+SI+200H]，在执行时，(DS)=1000H，(BX)=2100H，(SI)=0010H，内存单元12310H的内容为1234H。问该指令执行后，AX的值是什么？

解：根据相对基址加变址寻址方式的规则，在执行本例指令时，源操作数的有效地址EA为：

EA=(BX)+(SI)+200H=2100H+0010H+200H=2310H

该操作数的物理地址应由DS和EA的值形成，即：

PA=(DS)*16+EA=1000H*16+2310H=12310H

所以，该指令的执行效果是：把从物理地址为12310H开始的一个字的值传送给AX。其执行过程如图3.7所示。

从相对基址加变址这种寻址方式来看，由于它的可变因素较多，看起来就显得复杂些，但正因为其可变因素多，它的灵活性也就很高。比如：

图3.7 相对基址加变址寻址方式的执行过程示意图

用D1[i]来访问一维数组D1的第i个元素，它的寻址有一个自由度，用D2[i][j]来访问二维数组D2的第i行、第j列的元素，其寻址有二个自由度。多一个可变的量，其寻址方式的灵活度也就相应提高了。

相对基址加变址寻址方式有多种等价的书写方式，下面的书写格式都是正确的，并且其寻址含义也是一致的。

MOV　AX, [BX+SI+1000H]　　　MOV　AX, 1000H[BX+SI]
MOV　AX, 1000H[BX][SI]　　　 MOV　AX, 1000H[SI][BX]

但书写格式BX [1000+SI]和SI[1000H+BX]等是错误的，即所用寄存器不能在“[“，”]”之外，该限制对寄存器相对寻址方式的书写也同样起作用。

相对基址加变址寻址方式是以上7种寻址方式中最复杂的一种寻址方式，它可变形为其它类型的存储器寻址方式。表3.1列举出该寻址方式与其它寻址方式之间的变形关系。

表3.1 相对基址加变址寻址方式与其它寻址方式之间的变形关系

源操作数 | 指令的变形 | 源操作数的寻址方式
只有偏移量 | MOV AX, [100H] | 直接寻址方式
只有一个寄存器 | MOV AX, [BX] 或 MOV AX, [SI] | 寄存器间接寻址方式
有一个寄存器和偏移量 | MOV AX, [BX+100H] 或 MOV AX, [SI+100H] | 寄存器相对寻址方式
有二个寄存器 | MOV AX, [BX+SI] | 基址加变址寻址方式
有二个寄存器和偏移量 | MOV AX, [BX+SI+100H] | 相对基址加变址寻址方式


3.8 32位地址的寻址方式

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 32位地址的寻址方式

3.8 32位地址的寻址方式

在32位微机系统中，除了支持前面的七种寻址方式外，又提供了一种更灵活、方便，但也更复杂的内存寻址方式，从而使内存地址的寻址范围得到了进一步扩大。

在用16位寄存器来访问存储单元时，只能使用基地址寄存器(BX和BP)和变址寄存器(SI和DI)来作为地址偏移量的一部分，但在用32位寄存器寻址时，不存在上述限制，所有32位寄存器(EAX、EBX、ECX、EDX、ESI、EDI、EBP和ESP)都可以是地址偏移量的一个组成部分。

当用32位地址偏移量进行寻址时，内存地址的偏移量可分为三部分：一个32位基址寄存器，一个可乘1、2、4或8的32位变址寄存器，一个8位/32位的偏移常量，并且这三部分还可进行任意组合，省去其中之一或之二。

32位基址寄存器是：EAX、EBX、ECX、EDX、ESI、EDI、EBP和ESP；
32位变址寄存器是：EAX、EBX、ECX、EDX、ESI、EDI和EBP(除ESP之外)。

下面列举几个32位地址寻址指令：

MOV AX, [123456H] | MOV EAX, [EBX]
MOV EBX, [ECX*2] | MOV EBX, [EAX+100H]
MOV EDX, [EAX*4+200H] | MOV EBX, [EAX+EDX*2]
MOV EBX, [EAX+EDX*2+300H] | MOV AX, [ESP]

用32位地址偏移量进行寻址的有效地址计算公式归纳如右式所示。

由于32位寻址方式能使用所有的通用寄存器，所以，和该有效地址相组合的段寄存器也就有新的规定。具体规定如下：

1、地址中寄存器的书写顺序决定该寄存器是基址寄存器，还是变址寄存器；

如：[EBX+EBP]中的EBX是基址寄存器，EBP是变址寄存器，而[EBP+EBX]中的EBP是基址寄存器，EBX是变址寄存器；

2、默认段寄存器的选用取决于基址寄存器；

3、基址寄存器是EBP或ESP时，默认的段寄存器是SS，否则，默认的段寄存器是DS；

4、在指令中，如果使用段前缀的方式，那么，显式段寄存器优先。

下面列举几个32位地址寻址指令及其内存操作数的段寄存器。

指令的举例 | 访问内存单元所用的段寄存器
MOV AX, [123456H] | ；默认段寄存器DS
MOV EAX, [EBX+EBP] | ；默认段寄存器DS
MOV EBX, [EBP+EBX] | ；默认段寄存器SS
MOV EBX, [EAX+100H] | ；默认段寄存器DS
MOV EDX, ES:[EAX*4+200H] | ；显式段寄存器ES
MOV [ESP+EDX*2], AX | ；默认段寄存器SS
MOV EBX, GS:[EAX+EDX*2+300H] | ；显式段寄存器GS
MOV AX, [ESP] | ；默认段寄存器SS

3.9 操作数寻址方式的小结

下面控件是学习和理解操作数寻址方式的控件，它把各种寻址方式的变化和限制组合在一起。通过它，读者能更进一步掌握寻址方式的书写格式。


3.10 习题

本章的学习内容和要求
本章的学习内容和要求
3.1 立即寻址方式
3.1 立即寻址方式
3.2 寄存器寻址方式
3.2 寄存器寻址方式
3.3 直接寻址方式
3.3 直接寻址方式
3.4 寄存器间接寻址方式
3.4 寄存器间接寻址方式
3.5 寄存器相对寻址方式
3.5 寄存器相对寻址方式
3.6 基址加变址寻址方式
3.6 基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.7 相对基址加变址寻址方式
3.8 32位地址的寻址方式
3.8 32位地址的寻址方式
3.9 操作数寻址方式的小结
3.9 操作数寻址方式的小结
3.10 习题
3.10 习题

→ 习题

3.10 习题

3.1、访问内存单元的寻址方式有几种？它们具体是哪些？
3.2、指出下列各种操作数的寻址方式。
1)、[BX] 3)、435H 5)、[23] 7)、[DI+32] 9)、[EAX+90] | 2)、SI 4)、[BP+DI+123] 6)、data (data是一个内存变量名) 8)、[BX+SI] 10)、[BP+4]
3.3、哪些寄存器的值可用于表示内存单元的偏移量？
3.4、判断下列操作数的寻址方式的正确性，对正确的，指出其寻址方式，对错误的，说明其错误原因。
1)、[AX] 3)、BP 5)、DS 7)、[BX+BP+32] 9)、[CX+90] 11)、BX+90H 13)、SI[100h] 15)、[EAX+EBX*6] | 2)、[EAX] 4)、[SI+DI] 6)、BH 8)、[BL+44] 10)、EDX 12)、[DX] 14)、[BX*4] 16)、[DX+90H]
3.5、已知寄存器EBX、DI和BP的值分别为12345H、0FFF0H和42H，试分别计算出下列各操作数的有效地址。
1)、[BX] 3)、[BP+DI] 5)、[1234H] | 2)、[DI+123H] 4)、[BX+DI+200H] 6)、[EBX*2+345H]
3.6、指出下列各寻址方式所使用的段寄存器。
1)、[SI+34h] 3)、ES:[BP+DI] 5)、[BP+1234H] | 2)、[456H] 4)、[BX+DI+200H] 6)、FS:[EBX*2+345H]


第4章 标识符和表达式

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

第4章 标识符和表达式

标识符和表达式是程序设计经常用到的两个基本概念。在用高级语言进行程序设计时，如果程序要对某个变化的量进行处理时，通常都要对该变化量定义一个具有某种数据类型的符号名，用该符号名也就等于使用了该变化量。在汇编语言中，也是如此，所不同的是它们的说明和引用方式不同。

4.1 标识符

在汇编语言中，标号、内存变量名、子程序名和宏名等都是标识符，它一般最多由31个字母、数字及规定的特殊字符(?、@、_、$)等组成，并且不能用数字开头。通常情况下，汇编语言不区分标识符中字母的大小写。

和高级语言的变量名一样，一般要求标识符尽可能取得有点含义，这会大大改善程序的可读性，并有助于对程序的理解。但标识符不能是汇编语言的保留字，汇编语言的保留字主要是指：指令助忆符、伪指令定义符、寄存器名以及一些具有特殊含义的字符串等。

例如：MSG1、ERRMSG2、ASC1、asc2等是合法的标识符，而1a、ah、mov等就不是合法的标识符。

试比较ABCDH和0ABCDH之间的差异。前者是标识符，而后者是十六位进制数值。

4.2 简单内存变量的定义

在编程序时，我们往往要根据程序的需要定义一些内存单元。在高级语言程序中，要给存储单元取一个符号名，然后通过引用该符号名来访问其所对应的存储单元，而在汇编语言程序中要灵活一些，它可以给存储单元取符号名，也可以不取符号名。当给存储单元取符号名时，则可通过该符号名来访问其对应的存储单元；当不给存储单元取符号名时，则可通过存储单元的偏移量(有效地址)来访问它。

汇编语言中，常见的数据类型有字节、字和双字等。下面介绍如何定义各种整型类型的内存变量，有关浮点类型变量的定义方式将在第11章中介绍。

4.2.1 内存变量定义的一般形式

定义数据变量语句是在程序中经常使用的伪指令语句，其一般格式如下：

[变量名]　　数据定义符　　表达式1[, 表达式2, …, 表达式n]　　;注释

该定义格式的主要解释如下：
、 | 变量名必须是一个合法的标识符，它可以写，也可以不写；
、 | 数据定义符用于确定内存单元的数据类型，常用的定义符有：DB、DW和DD等；
、 | 表达式是定义内存单元时的初值表达式，一个定义语句可以有多个初值表达式，各表达式之间必须用逗号‘，’分开；如果某个存储单元没有初值表达式，则必须用一个问号‘？’来表示；
、 | 在定义语句的后面可以书写注释内容，也可以不写。

在定义变量时，虽然可以不写变量名，但我们建议还是要写，因为不写变量名，就意味着只能用内存单元的偏移量来访问它。这时，一旦内存单元的偏移量发生变化，那么，程序中的所有引用都要修改，这不仅增加了程序维护的工作量，而且也容易因遗漏修改而出错。


4.2.2 字节变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 内存变量定义 → 字节变量

4.2.2 字节变量

定义字节变量的定义符为DB/BYTE(Define Byte)，每个字节只占一个字节单元。其中：BYTE是MASM 6.0及其以后版本的数据类型说明符，随后的其它类型说明符同此说明。

例如：

COUNTER | DB 6
 | DB 'A', 'D', 0Dh, '$'
TABLE | DB 1, 3, 5, 7, 9, 11

上面的定义语句经汇编后所产生出的内存单元分配情况如图4.1所示。图中的数据是用十六进制表示的(以后也如此，不再说明)，由引号括起来的字符在内存中是存放其ASCII码值。所以，'D'和0Dh是不同的，前者是字符'D'，后者是数值12的十六进制编码。

…

06

41

44

0D

24

01

03

05

07

09

0B

…

COUNTER | TABLE

图4.1 内存单元的分配情况示意图

注意：在上例中，说明语句“DB 'A', 'D', 0Dh, '$'”之前并没有给出变量名，但我们可以从前面的变量名COUNTER一直往后数，或从TABLE往前数，来访问某存储单元，因为它们是一片连续的存储单元，这和高级语言的变量定义有点区别的。在高级语言中，我们一定要用某个标识符来说明变量，也必须用该变量名来访问其所对应的存储单元。

用定义符DB还可定义一种特殊的数据形式——字符串。在定义字符串时，必须用成对的单引号或双引号把所要的字符括起来，括号内字符的ASCII码将依次存放在相应的字节单元内。例如：

MSG1　DB　'I am a student.'

该说明语句所对应的存储单元分布如下所示。为了看起来方便，并没有用字符的ASCII码来存放在相应的存储单元内，而直接用该字符，请不要引起误解。

… 'I' ' ' 'a' 'm' ' ' 'a' ' ' 's' 't' 'u' 'd' 'e' 'n' 't' …

上面的例子也可改写为另一种等价的语句：

MSG1　DB　'I', ' ', 'a', 'm', ' ', 'a', ' ', 's', 't', 'u', 'd', 'e', 'n', 't', '.'

显然，前者的说明要比后者方便得多，所以，在程序中都采用前者的书写方式。


4.2.3-4 字/双字变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 内存变量定义 → 字变量

4.2.3 字变量

定义字变量的定义符为DW/WORD(Define Word)，每个字占用两个连续的字节单元。

例如：

Word1　DW　89H, 1909H, -1
DW　0abcdH, ?, 0

上述定义的内存分配如下所示。

… 89 00 09 19 FF FF CD AB -- -- 00 00 …

由于字变量的数据是按照“高高低低”的原则存于存储单元之中的，而字节数据是按照排列顺序存于存储单元中的，所以，它们的存储方式有所不同。

试比较下面两个定义的存储顺序，其中：41H和42H分别是'A'和'B'的ASCII码。

B1　DB　'AB'

W1　DW　'AB'
… 41h 42h 42h 41h …

4.2.4 双字变量

定义双字变量的定义符为DD/DWORD(Define Doubleword)，每个双字变量占用二个连续的字单元(四个字节)。

DW1　DD　12345678H, ?
DW2　DD　0abcd1243H

上述定义的内存分配如下所示。

… 78 56 34 12 -- -- -- -- 43 12 CD AB …


4.2.5 多字变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 内存变量定义 → 字变量

4.2.5 六字节变量

定义六字节变量的定义符为DF/FWORD(Define Farword)。顾名思义，每个六字节变量占用六个连续的字节。

DF1　DF　1234567890abH, -1
DF　1abcd23H

上述定义的内存分配如下所示。

… abH 90H 78H 56H 34H 12H 0FFH 0FFH 0FFH 0FFH 0FFH 0FFH 23hH 0cdH 0abH 01H 00H 00H …

4.2.6 八字节变量

定义八字节变量的定义符为DQ/QWORD(Define Quadword)。同理，每个八字节变量占用八个连续的字节。

DQ1　DQ　12345678H, 0H, -1234H
DQ　?, 1238H, ?

第一个八字节常量12345678H在内存中的分配方式如下所示，其存储原则与前面相同。其它八字节常量的存储方式与此一致。

…

78

 56 34 12 00 00 00 00 …

4.2.7 十字节变量

定义十字节变量的定义符为DT/TBYTE(Define Tenbytes)。同理，每个十字节变量占用十个连续的字节。

DT1　DT　12345678H, 0H, -1234H
DT2　DT　?, -1H

第一个十字节常量12345678H在内存中的分配方式如下所示，它同样按“高高低低”的原则来存储。其它十字节常量的存储方式与此一致。

…

78

 56 34 12 00 00 00 00 00 00 …

以上六个数据类型是汇编语言中最基本的数据类型，其中，前三个是在程序中经常使用的，后三个的使用频率不太高。


4.3 调整偏移量的伪指令

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 调整偏移量伪指令

4.3 调整偏移量伪指令

调整偏移量伪指令是在内存变量定义时用来调整内存变量起始偏移量的，它们是在把源程序汇编成目标文件时起作用。常用的调整偏移量伪指令有：EVEN、ALIGN和ORG，它们的主要目的是：为了更有效地读取内存单元的内容。

4.3.1 偶对齐伪指令EVEN

偶对齐伪指令格式：

EVEN

伪指令的作用是：告诉汇编程序(Assember)，本伪指令下面的内存变量从下一个偶地址单元开始分配。

如果下一个偏移量是偶地址，那么，该伪指令不起作用，否则，汇编程序将空出一个字节，从下一偶地址开始为其后变量分配内存单元。

假设有下列变量定义，并且变量B1的偏移量是偶数，其内存单元分布如图4.2所示。

B1 | DB | 12H | ;为了表示方便，不妨再假设其偏移量为：xxxx0H
W1 | DW | 4567H
图4.2 内存单元分布图 | 图4.3 齐偏移量读取一个字的过程示意图

在上述定义情况下，在许多微机系统中，当需要读变量W1及其后面的字内容时，硬件将按图4.3所示的方式分二次读出该字内容，再拼接成一个字内容，这时，无疑需要二个读内存周期，从而影响程序执行的速度。

出现上述问题的主要原因就是字变量W1在数据段内的偏移量是奇数，为了保证其偏移量是偶数，需要在其定义之前加上伪指令EVEN。 所以，可把前面的变量定义改变成下列形式： |  |  |  | 图4.4 偶偏移量读取字过程
 | B1 | DB | 12H
 |  | EVEN
 | W1 | DW | 4567H
这时，变量的内存分配和读取字变量W1的过程如图4.4所示。

4.3.2 对齐伪指令ALIGN

对齐伪指令格式：

ALIGN Num

其中：Num必须是2的幂，如：2、4、8和16等。

伪指令的作用是：告诉汇编程序，本伪指令下面的内存变量必须从下一个能被Num整除的地址开始分配。

如果下一个地址正好能被Num整除，那么，该伪指令不起作用，否则，汇编程序将空出若干个字节，直到下一个地址能被Num整除为止。

试比较下面二组变量定义，它们的对齐效果一致吗？

B1 | DB 12H | B1 | DB 12H
 | EVEN |  | ALIGN 2
W1 | DW 4567H | W1 | DW 4567H

从上面的对比，我们不难看出：伪指令ALIGN的说明功能要比伪指令EVEN强。


4.3.3 调整偏移量伪指令ORG

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 调整偏移量伪指令 → ORG

4.3.3 调整偏移量伪指令ORG

调整偏移量伪指令格式：

ORG 数值表达式

伪指令的作用是：告诉汇编程序，本伪指令下面的内存变量从该“数值表达式”所指定的地址开始分配。

假设有下列变量定义，并且变量word1的偏移量为0。

word1 | DW | 1234h |
byte1 | DB | 56h
word2 | DW | 0abcdh
 | ORG | 1
byte2 | DB | ?
word3 | DW | ?
byte3 | DB | ? | 图4.5 内存变量分配示意图

前三个变量定义的内存分布如图4.5的左边所示，但由于伪指令“ORG 1”的作用，说明其后面所说明的变量要从偏移量为“1”的内存单元开始存放。所以，后三个变量的内存分配如图4.5的右边所示。

由图4.5可见，这些变量的内存分配是相互重叠的，对某个变量的操作无疑会影响到与之重叠的变量。

另外，变量byte2、word3和byte3没有赋初值，如果赋初值的话，则重叠部分的内存单元的原来初值将被覆盖掉。

在以上三个伪指令EVEN、ALIGN和ORG中，伪指令EVEN的使用频率较高。

4.3.4 偏移量计数器的值

前面，我们介绍了几种改变偏移量计数器之值的方法，但在程序中还无法引用其值。汇编语言提供了一个特殊的符号“$”来引用偏移量计数器的值。

例如： |  |  | 图4.6 内存单元分布示意图
W1 | DW | $, $
 | ORG | $+3 ;从当前地址开始空3个字节
B1 | DB | 43h
假设：在给变量W1分配内存单元时，当前偏移量计数器的值为2。 于是，变量W1后面第一个“$”代表数值2，第一个字分配后，此时偏移量计数器$的值就为4，所以，第二个“$”就代表数值4。 在分配完二个字之后，偏移量计数器的值变为6，$+3的值为9，所以，伪指令“ORG $+3”就表示下一个变量从偏移量为9的单元地址开始分配。 综上分析，上述变量说明所对应的内存单元分布如图4.6所示。


4.4 复合内存变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义

4.4 复合内存变量的定义

上节，我们介绍了汇编语言中六个最基本的数据类型，这些数据类型能满足程序设计中绝大多数情况的需要，但也存在需要更复杂的数据类型的情况。

下面介绍汇编语言所提供的三种复合数据类型的说明形式。

4.4.1 重复说明符DUP

从前面的内容里，我们知道了定义少量内存变量的定义形式，但如果在程序中要说明50个、100个、200个甚至更多的、同类型的内存变量时，若采用前面所学的方法，对它们一一加以说明显然是不可行的。为此，汇编语言提供了变量的重复说明符DUP，其说明的一般形式如下：

count DUP (表达式, 表达式, …, 表达式)

解释：count是重复次数，(表达式, 表达式, …, 表达式)是被重复的部分，“表达式”可以是存储单元的初值，也可以是含义另一个DUP的式子。如果在表达式的括号中有多个表达式，那么，它们之间要用逗号','分开。

例如：

BUFFER | DB | 100 DUP(?)
STRING | DB | 120 DUP('ABCDE'), 0
DATA1 | DW | 50 DUP(10H, 20 DUP(1,2,3), 20H)
POINTS | DD | 12, 30 DUP(0)

从上面的例子可看出：用DUP说明内存变量相当于在高级语言中定义数组。


4.4.2 结构类型的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义 → 结构类型的定义

4.4.2 结构类型的定义

重复说明符DUP只能用于重复同一数据类型的变量说明，它不可以重复不同数据类型的变量说明。为了把一组不同类型的变量说明组合在一起，汇编语言提供了另一种复合数据类型说明符——结构类型说明符STRUC。

1、结构类型的定义

用STRUC和ENDS可以把一系列数据定义语句括起来作为一种新的、用户定义的结构类型。其一般说明格式如下：

结构名　STRUC [Alignment][, NONUNIQUE]

数据定义语句序列

结构名　ENDS

解释：结构名是一个合法的标识符，且具有唯一性。结构名代表整个结构类型，前后两个结构名必须一致。结构内被定义的变量为结构字段，变量名即为字段名。

一个结构中允许含有任意多个字段，各字段的类型和所占字节数也都可任意。如果字段有字段名，则字段名必须唯一。每个字段可独立存取。

、对齐方式(Alignment)：可用1、2或4来指定结构中字段的字节边界(Byte boundary)，其缺省值为1。见4.3.2节中的有关叙述；

、NONUNIQUE：要求结构中的字段必须用全名才能访问，见本小节中的“结构类型字段的引用”。

例如：
COURSE | STRUC |  | 在左上例中，COURSE是结构名，它含有三个字段：NO、CNAME和SCORE，它们的类型分别是DD、DB和DW。
 | NO | DD ?
 | CNAME | DB 'Assember'
 | SCORE | DW 0
COURSE | ENDS

上例中，COURSE是结构名，它含有三个字段：NO、CNAME和SCORE，这些字段的类型分别是DD、DB和DW。结构COURSE的字段分布如图4.7所示。

            A s s e m b e r
NO CNAME SCORE

图4.7 结构类型COURSE的字段分布示意图

从图4.7，我们不难看出：结构类型COURSE共占14个字节，其字段NO、CNAME和SCORE的偏移量分别为：0、4和12。

结构中的字段可以有字段名，也可以没有字段名。有字段名的字段可直接用该字段名来访问它，没有字段名的字段可以用该字段在结构中的偏移量来访问。

例如：
PEASON | STRUC
 | NO | DD ? | ;偏移量为0
 | NAME | DB 10 dup (?) | ;偏移量为4
 |  | DB 1 | ;偏移量为14
PEASOM | ENDS

在结构PEASON中，有二个字段有字段名，一个字段没有字段名，但不管有无字段名，我们都可用其偏移量来访问它。


4.4.2 结构变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 结构类型的定义 → 结构变量的定义

2、结构类型变量的定义

在定义某个结构类型后，程序员就可以说明该结构类型的内存变量。它的说明形式与前面介绍的简单数据类型的变量说明基本上一致。其定义格式如下：

[变量名]　结构名　<[字段值表]>

解释：1)、 | 变量名即为该结构类型的变量名，它可省缺。如果省缺，则不能用符号名来访问该内存单元；
2)、 | 字段值表是给字段赋初值，中间用逗号','分开，其字段值的排列顺序及类型应与该结构说明时各字段相一致；
3)、 | 如果结构变量中某字段用其说明时的缺省值，那么，可用逗号来表示；如果所有字段都如此，则可省去字段值表，但必须保留一对尖括号"<"、">"。
例如：
COURSE1 | COURSE <> |  | ;使用缺省的初值
COURSE2 | COURSE <1, 'Pascal', 60>
COURSE3 | COURSE <2, , 84> |  | ;使用缺省的课程名
PEASON1 | PEASON | <1000, '张 三', 34>

3、结构类型字段的引用

定义了结构类型的变量后，若要访问其结构中的某个字段，则可采用如下形式：

结构变量名.字段名

该引用方式与高级语言的字段引用方式完全一致，我们还可用偏移量来访问其中的某个字段，但此方法不直观，变动性大，所以，一般情况下，不提倡使用此方法。

例如：

EXAM1 | STRUC
 | F1 | DW ?
 | F2 | DB ?
 | EVEN |  | ;偶对齐
 | F3 | DW ?
EXAM1 | ENDS
 | E1 | EXAM1 <1234H,'A',8765H> | ;定义结构EXAM1的一个变量E1

下面二种方法都可以把结构变量E1中字段的内容赋给寄存器AX，但如果在字段F3之前增加或减少了字段，那么，这些引用需要改变吗？

(1)、用字段名直接引用

MOV　AX, E1.F3

(2)、用字段的偏移量间接引用

LEA　SI, E1
MOV　AX, [SI+4]　　　　　;其中4是字段F3的偏移量


4.4.3 联合类型的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义 → 联合类型的定义

4.4.3 联合类型的定义

联合数据类型是一种特殊的数据类型。它可以实现：以一种数据类型存储数据，以另一种数据类型来读取数据。程序员可以根据不同的需要，以不同的数据类型来读取联合类型中的数据。也就是说，在一些情况下，以一种数据类型来读取联合类型中的数据，而在另一些情况下，又以另一种数据类型来读取其数据。

1、联合类型的说明

联合数据类型其说明格式如下：

[联合类型名]　UNION [Alignment] [，NONUNIQUE]

数据定义语句序列

[联合类型名]　ENDS

联合类型中的各字段相互覆盖，即同样的存储单元被多个不同的字段所对应，并且其每个字段的偏移量都为0。

联合类型所占的字节数是其所有字段所占字节数的最大值。

、对齐方式(Alignment)：可用1、2或4来指定结构字节的边界，其缺省值为1。它还用可伪指令ALIGN或EVEN来重新定界，也可用命令行选项/Zp来定界；

、NONUNIQUE：要求联合类型中的字段必须用全名才能访问，引用联合类型字段的方法见下面的“联合类型字段的引用”。

例如：

DATATYPE | UNION
 | BB DB ? | ;定义一个字节类型的字段
 | WW DW ? | ;定义一个字类型的字段
 | DD DD ? | ;定义一个双字类型的字段
DATATYPE | ENDS

联合类型DATATYPE的字段分布如图4.8所示。

在联合类型的最外层定义中，在伪指令UNION和ENDS的前面一定要书写该联合类型名，而在其嵌套定义的内层，伪指令UNION和ENDS之前一定不能写联合类型名。

图4.8 DATATYPE的字段分布示意图

例如：

UNION1 | UNION
 | BB | DB ?
 | WW | DW ?
 | UNION |  | ;联合类型的嵌套定义形式
 | W1 | DW ?
 | B1 | DB ? |
 | ENDS
UNION1 | ENDS


4.4.3 联合变量的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义 → 联合变量的定义

2、联合类型变量的定义

联合数据类型的变量只能用第一个字段的数据类型来进行初始化。

例如：

U1 | DATATYPE | <'J'> | ;定义一个联合变量，并初始化其值
U2 | DATATYPE | <1234H> | ;初始化错误，只能用字节数据来初始化
U3 | UNION1 | <1>

3、联合类型字段的引用

定义了联合类型的变量后，就可根据需要，以不同的数据类型或字段名来存取该联合类型中的数据。引用其字段的具体形式如下：

联合类型变量名.字段名

例如：

MOV | U1.WW, 1234H | ;给联合类型变量赋字数据
MOV | AL, U1.BB | ;AL=34H
MOV | BX, U1.WW | ;BX=1234H
MOV | U1.BB, 'A' | ;U1的值1241H，41H是'A'的ASCII码


4.4.3 记录类型的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义 → 记录类型的定义

4.4.4 记录类型的定义

1、记录类型的说明

汇编语言的记录类型与高级语言的记录类型不同，它是为按二进制位存取数据提供方便的。记录类型的说明要用到另一个保留字RECORD，其说明格式如下：

记录名　RECORD　字段 [, 字段, ……]

其中“字段”代表：字段名:宽度[=初值表达式]

解释：

 1、记录名代表该记录类型；
2、记录类型可以由多个字段组成，每个字段之间要用逗号','分开；
3、字段的属性包括字段名、宽度和初值；

4、字段的“宽度”表示该字段所占的二进制位数，它必须是一个常数，并且所有字段的宽度之和不能大于16；如果记录的总宽度大于8，则系统为该记录类型分配二个字节，否则，只分配一个字节；
记录的最后一个字段排在所分配空间的最低位，然后对记录中的字段依次“从右向左”分配二进制位，左边没有分完的二进制位补0；

5、初值表达式给出的是该字段的缺省值。如果初值超过了该字段的表示范围，那么，在汇编时将产生错误提示信息；如果某字段没有初值表达式，则其初值为0。

例如：

COLOR | RECORD BLINK:1, BACK:3=0, INTENSE:1=1, FORE:3
FLOAT | RECORD DSIGN:1, DATA:8, ESIGN:1, EXP:4
记录类型COLOR有四个字段：BLINK、BACK、INTENSE和FORE，它们的宽度分别为：1、3、1和3，所以，该记录类型共有8位二进制，系统分配给它一个字节。 记录类型COLOR的二进制位分布如右图4.9所示。 | 图4.9 记录类型COLOR的二进制位分配示意图
记录类型FLOAT用来模仿《计算机原理》中的浮点数表示法，它也有四个字段： DSIGN (尾数的符号位)； DATA (尾数)； ESIGN (指数的符号位)； EXP (指数)。 | 图4.10 记录类型FLOAT的二进制位分配示意图

它们的总宽度是14，所以，系统要给它分配二个字节。记录类型FLOAT的二进制位分布如右图4.10所示。


4.4.3 记录类型的定义

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 复合变量的定义 → 记录类型的定义

2、记录变量的定义

在程序中，必须先说明记录类型，然后才能定义该记录类型的变量。记录变量是把其二进制位分成一个或多个字段的字节或字变量。其定义格式与其它类型变量的定义方式类似，具体如下：

[变量名]　 记录名　<[字段值表]>

解释：

 1、变量名即为该记录类型的变量名，它可省缺。如果省缺，则不能用符号名来访问该内存单元；

2、字段值表是给字段赋初值，中间用逗号','分开，其字段值的排列顺序及大小应与该记录说明时各字段相一致；

3、如果记录变量的某字段用其说明时的缺省值，那么，可用逗号来表示；如果所有字段都如此，则可省去字段值表，但必须保留一对尖括号"<"、">"。

例如：

COLOR1 | COLOR <>, <1, 7, 0, 5>, <1, , 0, 7>
FLOAT1 | FLOAT <1, 23H, 0, 3>, <0, 89H, 1, 5>

3、记录的专用操作符

操作符WIDTH和MASK是作用于记录类型的两个专用保留字，利用它们可得到记录类型的不同属性。

  • 操作符WIDTH

操作符WIDTH返回记录或其字段的二进制位数，即其宽度。其一般书写格式如下：

WIDTH 记录名　或　WIDTH 记录字段名

假设有前面定义的记录类型COLOR，那么，WIDTH COLOR的值为8，WIDTH BACK的值为3，WIDTH BLINK的值为1。

  • 操作符MASK

操作符MASK返回一个8位或16位二进制数。在该二进制数中，被指定记录或字段使用的对应位的值为1，否则，其值为0。其一般书写格式如下：

MASK　记录名　或　MASK　记录字段名

假设有前面定义的记录类型FLOAT，那么，MASK EXP的值为000FH，MASK DATA的值为1FE0H，WIDTH DSIGN的值为2000H。

  • 记录字段

记录字段名是一个特殊的操作符，它本身也是操作数，其返回值是该字段移到所在记录的最低位所需要的位数，即该字段最低位在记录中的位置。

假设有前面定义的记录类型FLOAT，那么，有：

MOV CL, EXP | 相当于 | MOV CL, 0
MOV CL, DATA | 相当于 | MOV CL, 5


4.5 标号、属性

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 标号、变量和标号的属性

4.5 标号

标号是一种特殊的标识符，它代表代码段中的某个具体位置，它主要用于表明转移的目标位置。其说明形式如下：

标号:　　汇编语言指令　　　;注释

解释：标号必须是一个合法的标识符，在其后面紧跟一个冒号":"，冒号与汇编语言指令之间要有分隔符。通常用若干个空格、TAB来作分隔符，一般用分隔符使有关内容对齐为宜。

4.6 内存变量和标号的属性

变量是一个符号地质，其值会根据其数据类型来对应从该地址以后的若干个存储单元中所存的数值。标号也是一个符号地址，它所对应的存储单元中存放的是指令代码。虽然它们在某些性质上有所不同，但它们都是一个符号地址，代表一个存储单元的地址，所以，它们都具有存储单元的属性。除此之外，它们还有各自特殊的属性。

下面介绍内存变量和标号的属性及其有关操作符。

4.6.1 段属性操作符

段属性操作符(SEG)返回该标识符所在段的段地址。我们一般只会取内存变量所在段的段地址，而很少取标号所在段的段地址。

假设有下面变量定义：
 | …
 | SCORE DW ?
 | NAME DB 10 DUP(10) | ;数据段的变量定义
 | …
 | MOV AX, SEG SCORE | ;代码段的指令
 | MOV BX, SEG NAME

由于SCORE和NAME在同一段中定义，所以，寄存器AX和BX的值是相等的。

4.6.2 偏移量属性操作符

偏移量属性操作符(OFFSET)返回该标识符离它所在段的段地址有多少字节。一般情况，程序员只会取内存变量的偏移量，而不太关心标号的偏移量。

假设有下面变量定义：

FIRST DD 12345678H, 0 | ;数据段的变量定义
SCORE DW ?, 12H
NAME DB 10 DUP(10)
…
MOV AX, OFFSET SCORE | ;代码段的指令
MOV BX, OFFSET NAME
…

假设FIRST是数据段的第一个被定义的变量名，它的偏移量为0，SCORE的偏移量为8，因为它要跳过二个双字，其它如此类推。

由于NAME在SCORE之后，且SCORE之后有二个字，占四个字节，所以，BX的值要比AX的值大4。


4.6.3 类型属性操作符

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 变量和标号的属性 → 类型属性操作符

4.6.3 类型属性操作符

类型属性操作符(TYPE)是返回该变量所占字节数，或标号的“远”(FAR)、“近”(NEAR)类型。常用标识符的类型值如表4.1所列。

表4.1 常用标识符的类型值列表

标识符种类 | 字节变量 | 字变量 | 双字变量 | 近标号(NEAR) | 远标号(FAR)
TYPE的值 | 1 | 2 | 4 | -1 | -2

例如：
PEASON | STRUC
 | NO | DD | ?
 | NAME | DB | 10 dup (?)
 |  | DW | 1
PEASOM | ENDS
…
B1 | DB | 1, 2, 3
W1 | DW | 200 DUP(1,2,30 DUP(10,20)), 101H, -1
PEOPLE | PEASON | <>

按属性TYPE的含义，TYPE B1、TYPE W1和TYPE PEOPLE的值分别为：1，2和16。

4.6.4 长度属性操作符

长度属性操作符(LENGTH)是针对内存变量的操作符，它返回重复操作符DUP中的重复数。如果有嵌套的DUP，则只返回最外层的重复数；如果没有操作符DUP，则返回1。

如上例所示，根据属性LENGTH的含义，LENGTH B1、LENGTH W1和LENGTH PEOPLE的值分别为：1，200和1。

4.6.5 容量属性操作符

容量属性操作符(SIZE)也是针对内存变量的操作符。它的返回值按下列公式计算：

SIZE 变量 ＝ (LENGTH 变量) × (TYPE 变量)

如上例所示，SIZE B1、SIZE W1和SIZE PEOPLE的值分别为：1，400和16。


4.6.6 强制属性操作符

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 变量和标号的属性 → 强制属性操作符

4.6.6 强制属性操作符

在程序中，我们有时需要对同一个存储单元以不同的属性来访问，或对一些不确定的存储属性需要显式指定等，这时，我们就需要强制属性操作符PTR。该操作符的作用有点象C语言中的类型强制方法。

对于指令：MOV [BX], 1H，其目标操作数[BX]是寄存器间接寻址方式，它指向一个存储单元。在作传送操作时，是把“1H”扩展成8位作字节传送，还是扩展成16位作字传送呢？这就使该指令具有二义性，因为[BX]指向的存储单元可以字节或字的首地址。含有该指令的程序在汇编时，可能会产生警告或出错信息。

为了使指令中存储单元操作数具有明确的属性，我们可以使用强制属性操作符PTR。其一般格式为：

数据类型　PTR　地址表达式

其中：数据类型是前面所学的各种数据类型，常用的数据类型有：BYTE、WORD、DWORD、NEAR和FAR等。

为了明确指令中存储单元的属性，可把指令“MOV [BX], 1H”可改写成：

MOV byte ptr [BX], 1H 　　或　　MOV word ptr [BX], 1H

在指令中用操作符PTR强制后，不管其后的地址表达式原数据类型是什么，在本指令中就以PTR前面的类型为准。该强制属性只在本指令有效，是一种临时性的属性，它不会改变原内存单元的定义属性。

例如：
 | W1 | DW 1234H, 5678H
 | B1 | DB 2
 |  | DB 5
 | D1 | DD 23456789H
 | …
 | MOV | AX, word ptr b1 | ;把B1开始的二个字节拼接成一个字，执行后，(AX)=0502H
 | MOV | BH, byte ptr w1 | ;把字W1的低字节传送给BH，执行后，(BH)=34H
 | MOV | CH, byte ptr w1+1 | ;把字W1的高字节传送给CH，执行后，(CH)=12H
 | MOV | word ptr d1, 12H | ;把双字D1的低字修改成0012H，执行后，(D1)=23450012H

上面指令中的强制属性是临时属性，它不能改变这些变量在定义时的永久属性。


4.6.7 存储单元别名操作符

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 变量和标号的属性 → 存储单元别名操作符

4.6.7 存储单元别名操作符

由上一节的内容，我们知道：在程序中，如果需要以另一种数据类型来访问某一存储单元时，可用强制属性操作符PTR来实现。但如果在程序中要经常以某种其它的数据类型来访问该存储单元的话，那么，就必须在每次访问时都要加上强制属性操作符PTR。这样做虽然可行，但在编写程序时就显得比较麻烦。

为了克服上述不便，汇编语言提供了另一种操作符THIS，它为同一存储单元取另一别名，该别名可具有其自身的数据属性，但其段地址和偏移量是不变的。

操作符THIS的一般格式为：

THIS 数据类型

其中：数据类型是前面所学的各种数据类型，常用的数据类型有：BYTE、WORD、DWORD、NEAR和FAR等。

例如：
 | WBUFFER | EQU | THIS WORD | ;EQU是一个等价符号定义语句，在后面有介绍
 | BUFFER | DB | 20 DUP(?)

上述定义后，它们的逻辑关系如图4.11所示。

…… 　 　 …… 　 　 ……

图4.11 存储单元别名操作符THIS的效果示意图

如图4.11所示，这样就给同一片存储单元，取了二个具有不同数据类型的变量名。于是，在指令中，引用不同的变量名，就使用其不同的数据属性：

、如果引用变量名WBUFFER，则是按“字”属性来访问；
、如果引用变量名BUFFER，则是按“字节”属性来访问。

如此一来，指令“MOV　AX, word ptr BUFFER”和“MOV　AX, WBUFFER”是等效的，所不同的是：当以“字”属性访问BUFFER存储区时，不必使用强制属性符PTR，而改用“字”属性变量WBUFFER即可。


4.7 表达式

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 表达式

4.7 表达式

表达式是程序设计课程里的一个重要的基本概念，它可由运算符、操作符、括号、常量和一些符号连在一起的式子。在汇编语言中，表达式分为：数值表达式和地址表达式。

4.7.1 进制伪指令RADIX

伪指令RADIX用来设置整数的缺省进制，宏汇编开始时所默认的整数进制为十进制。该伪指令的使用格式如下：

.RADIX exp

其中：伪指令前面要用点‘.’开始，exp的值必须是区间[2, 16]内的一个整数。

该伪指令说明其下面整数的默认进制为exp。如果某整数已显式地表明了其进制，则该默认进制对其不起作用。在源文件中，可以使用多个RADIX伪指令来分别说明其后整数的默认进制，但为了避免引起不必要误会，我们不提倡这样去做。

例如：
 | .radix | 8
 | B1 | DB | 10, 11, 12 | ;这三个数是八进制数
 |  | DB | 10D | ;这数是十进制数，因为它已用'D'明确说明而不使用缺省进制
 | …
 | .radix | 10
 | MOV | AX, 1234 |  | ;1234是十进制数
 | MOV | AX, 1234H |  | ;1234H是十六进制数

思考题：

.radix　16
DW　　90D, 101B　　　;前者是十进制数，后者是二进制数吗？


4.7.2 数值表达式

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 表达式 → 数值表达式

4.7.2 数值表达式

数值表达式是在汇编过程中能够由汇编程序计算其值的表达式，其组成部分在汇编时就能完全确定。它通常是一些常量的运算组合。

1、常量

常量是一个立即数，直接写在汇编语言语句中，在程序的执行过程中，它不可能发生变化。通常，我们用二进制、八进制、十进制或十六进制来书写常量。

例如：10101011B、324Q、1234D、1234H、0abcdH、'AB'等都是常量。

在程序中，我们还可用伪指令.RADIX来改变数据的基数，在后面再详细讲解。

2、算术运算符

算术运算符包括符号：+(正)、-(负)，运算符：+(加)、-(减)、*(乘)、/(除)和MOD(取模)。这些运算符和常量、括号可组成数值表达式。

如：120+(321-90) mod 3，322*5/32，0abcdH+5，-590等

3、关系运算符

关系运算符包括符号：EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)和GE(大于等于)。这些关系运算符和常量、括号也可组成数值表达式。该表达式的计算结果规定如下：

若关系不成立，则该数值表达式的计算结果为0；否则，其结果为0FFFFH。

如：120H LT 100H+3，21H LE 21H等，它们的计算结果分别为：0和0FFFFH。

4、逻辑运算符

逻辑运算符包括按位操作符和移位操作符。具体是：AND(逻辑与)、OR(逻辑或)、NOT(逻辑非)、XOR(异或)、SHL(左移位)和SHR(右移位)。这些逻辑运算符和常量、括号可组成数值表达式。

如：1 SHL 3，47H AND 0FH，NOT 56H等，它们的计算结果分别为：8，7和0A9H。

5、表达式中的其它操作符

汇编语言中，还有其它可在数值表达式中使用的操作符。它们是：

、HIGH(高8位)、LOW(低8位)
、SEG(段地址)、OFFSET(偏移量)
、TYPE(标识符类型)、LENGTH(变量长度)、SIZE(变量容量)
、WIDTH(记录/记录字段宽度)、MASK(记录/记录字段的屏蔽位)等

在以上操作符中，只有HIGH和LOW没有介绍过，它们分别是选取表达式计算结果的高8位和低8位。其使用格式如下：

HIGH 表达式　　　LOW 表达式

如：HIGH (1234H+100H)，LOW 1234H等，它们的选取结果分别为：13H和34H。

6、运算符和操作符的优先级

在汇编语言中，有许多各种运算符和操作符，它们的优先级按从高到低的排列如下：

优先级：高 | LENGTH、SIZE、WIDTH、MASK、()、[]、.(用于结构字段)、<>(用于记录类型)
↓ ↓ | PTR、SEG、OFFSET、TYPE、THIS、:(用于段超越前缀)
 | *、/、MOD、SHL、SHR
 | HIGH、LOW
 | +、-
 | EQ、NE、LT、LE、GT、GE
 | NOT
 | AND
 | OR、XOR
优先级：低 | SHORT

这些符号及其优先级并不要强记它们，有些符号同时出现的可能性非常小。在以后的学习中对常用的几个加以运用也就记住了。


4.7.3 地址表达式

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 表达式 → 地址表达式

4.7.3 地址表达式

地址表达式是计算存储单元地址的表达式，它可由标号、变量名和由括号括起来的基址或变址寄存器组成。其计算结果表示一个存储单元的地址，而不是该存储单元的值。

例如：
 | B1 | DB | 10H, 11H, 12H
 |  | DB | 'ABCD'
 | W1 | DW | 1234H, 5678H

表达式B1+1、B1+3和W1+2等都是地址表达式，其值所代表的地址位置如图4.10所示。显然这些地址表达式所对应的存储内容分别为：11H、'A'和5678H。

… 10 11 12 'A' 'B' 'C' 'D' 34 12 78 56 …
B1 | B1+3 | W1+2

图4.10 地址表达式所对应的存储单元位置示意图

注意：地址表达式W1+1并不表示字变量W1之后一个字的存储单元，而是字变量W1之后一个字节的存储单元，它的存储单元值是：7812H。


4.8 符号定义语句

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 符号定义语句

4.8 符号定义语句

在程序中，我们经常要使用一些常量或数值表达式，并把它们直接写在指令中，但当需要修改时，就要对它们逐个进行修改，这无疑会增加维护程序的工作量，而且每个常量或表达式所代表的含义也容易遗忘。

为了改善程序的可读性，尽量减少维护程序的工作量，汇编语言提供了为常量或表达式定义一个符号名的方法。一旦定义了符号名，在指令中就可直接使用它们。

4.8.1 等价语句

1、一般格式

等价语句的一般使用格式如下：

符号名　EQU　表达式

作用是左边的符号名代表右边的表达式。

注意：等价语句不会给符号名分配存储空间，符号名不能与其它符号同名，也不能被重新定义。

2、用符号名代表常量或表达式

当把一个常量或表达式定义成一个具有一定含义的符号名后，在程序中就可以用该符号名来代表该常量或表达式。

例如：
 | NUMBER | EQU | 100 | ;给缓冲区的长度取一个符号名
 | BUFF_LEN | EQU | NUMBER+2
 | CR | EQU | 13 | ;给“回车”符的ASCII码定义一个符号名
 | LF | EQU | 10 | ;给“换行”符的ASCII码定义一个符号名
 | …
 | BUFFER | DB | NUMBER, ?, NUMBER DUP (?) | ;用符号名来定义缓冲区
 | …

3、用符号名代表字符串

用一个具有一定含义的符号名定义某一个较长的字符串，在随后的程序中就用该符号名。例如：

GREETING　EQU　"How are you!"

在该定义之后，就可使用符号名GREETING来代表字符串"How are you!"。

4、用符号名关键字或指令助忆符

用一个(组)程序员自己习惯的符号名来代替汇编语言中的关键字或指令助忆符。但在此建议不要这样做，因为程序的编写者习惯，程序的其他阅读者可能会觉得很别扭。

例如：

MOVE　　　 EQU　MOV　　　;给指令MOV取另一个符号名MOVE
COUNTER　 EQU　CX　　　　;给寄存器CX取一个叫“计数器”的符号名

上面的定义只是给原来的助忆符MOV和CX起了另一个别名，而原来助忆符MOV和CX仍然可以使用，所以，我们可编写如下语句：

MOVE　AX, CX　　　　　　　 ;相当于指令：MOV AX, CX
MOV　 COUNTER, BX　　　　;相当于指令：MOV CX, BX

5、用符号名定义存储单元的别名

可对一片存储单元定义另一个数据类型的符号名，有关叙述参见前面的操作符THIS。

例如：
 | WORD1 | EQU THIS WORD | ;给后面的字节存储单元取一个字属性的符号名
 | BYTE1 | DB 12h, 21h
 | FLAG | DW 1234H
 | FLAG1 | EQU byte ptr FLAG | ;给FLAG的低字节取一个字节属性的符号名
 | FLAG2 | EQU byte ptr FLAG+1 | ;给FLAG的高字节取一个字节属性的符号名

有了上述定义后，可编写如下语句：

MOV　AX, WORD1　　　　　 ;执行后，(AX)=2112H
MOV　BL, FLAG1　　　　　　;执行后，(BL)=34H


4.8.2 等号语句

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 符号定义语句 → 等号语句

4.8.2 等号语句

汇编语言提供了用等号来定义符号常数的方法，即可用符号名代表一个常数。其一般格式如下：

符号名＝数值表达式

数值表达式在汇编时应该可以计算出数值，它不能含有向前引用的符号名称。用等号语句定义的符号可以被重新定义。例如：

ABC = 10 + 200 * 5 | ;ABC的值为1010
ABC1 = 5 * ABC + 21 | ;ABC1的值为5071
COUNT = 1 | ;COUNT的值为1
COUNT = 2*COUNT + 1 | ;COUNT的值为3

4.8.3 符号名定义语句

符号名定义语句LABEL与前面的操作符THIS功能类似，该语句定义一个指定的符号名，该符号名的段地址和偏移量与下面紧跟存储单元的相应属性相同，但该符号的类型是新指定的。LABEL语句的一般格式如下：

符号名 LABEL 类型

其中：常用的类型有BYTE、WORD、DWORD、NEAR和FAR等。

例如：
NEXT: | …
 | WBUFFER LABEL WORD | ;WBUFFER与BUFFER具有相同的段地址和;偏移量，但它们的数据类型不同。
 | BUFFER DB 200 DUP(0)
 | …
 | NEXT1 LABEL FAR | ;NEXT1和NEXT具有相同的段地址和偏移量，;NEXT1是“远”标号，NEXT是“近”标号。
 | MOV AX, BX

标号NEXT1和NEXT可用于不同的情况。当在同一个模块内转移时，可使用标号NEXT；当在不同的模块之间进行转移时，需要使用“远”标号NEXT1。


4.9 习题

1 内存变量定义的一般形式
1 内存变量定义的一般形式
2 字节变量
2 字节变量
3 字变量
3 字变量
4 双字变量
4 双字变量
5 六字节变量
5 六字节变量
6 八字节变量
6 八字节变量
7 十字节变量
7 十字节变量
1 偶对齐伪指令EVEN
1 偶对齐伪指令EVEN
2 对齐伪指令ALIGN
2 对齐伪指令ALIGN
3 调整偏移量伪指令ORG
3 调整偏移量伪指令ORG
4 偏移量计数器的值
4 偏移量计数器的值
1 重复说明符DUP
1 重复说明符DUP
2 结构类型的定义
2 结构类型的定义
3 联合类型的定义
3 联合类型的定义
4 记录类型的定义
4 记录类型的定义
5 数据类型的自定义
5 数据类型的自定义
1 段属性操作符
1 段属性操作符
2 偏移量属性操作符
2 偏移量属性操作符
3 类型属性操作符
3 类型属性操作符
4 长度属性操作符
4 长度属性操作符
5 容量属性操作符
5 容量属性操作符
6 强制属性操作符
6 强制属性操作符
7 存储单元别名操作符
7 存储单元别名操作符
1 进制伪指令RADIX
1 进制伪指令RADIX
2 数值表达式
2 数值表达式
3 地址表达式
3 地址表达式
1 等价语句
1 等价语句
2 等号语句
2 等号语句
3 符号名定义语句
3 符号名定义语句
本章的学习内容和要求
本章的学习内容和要求
4.1 标识符
4.1 标识符
4.2 简单内存变量的定义
4.2 简单内存变量的定义
4.3 调整偏移量伪指令
4.3 调整偏移量伪指令
4.4 复合内存变量的定义
4.4 复合内存变量的定义
4.5 标号
4.5 标号
4.6 内存变量和标号的属性
4.6 内存变量和标号的属性
4.7 表达式
4.7 表达式
4.8 符号定义语句
4.8 符号定义语句
4.9 习题
4.9 习题

→ 习题

4.9 习题

4.1、判断下列标识符的合法性。

1)、code
3)、ABCDH
5)、eax
7)、A?@
9)、www@zsu.edu.cn
11)、_BX

 2)、Data
4)、1abcH
6)、???01
8)、www@zsu
10)、AX$DX
12)、CX

4.2、按下面要求写出相应的数据定义语句(未指定变量名的，可任意指定或省缺)。

1)、定义一个字节区域，第一个字节的值为20，其后跟20个初值为0的连续字节；
2)、定义一个以0为结束符的字符串，其初值为：The course is easy；
3)、定义一个以'$'为结束符的字符串，该串中含有换行符和回车符；
4)、定义100个字，其初值为0；
5)、从一个偶地址开始定义一个字变量word；

6)、先定义一个字符串String，其偏移量指定为10，其初值为"ABCDEF"，然后再定义3个字类型的数组Data，要求String和Data的偏移量相同；

7)、定义一个字符串String，其初值为"12345678"，再定义4个字类型的数组Data，要求String和Data共享同一片内存单元；

4.3、把下列C语言的变量定义语句改写成与之等价的汇编语言语句。

1)、char ch1, ch2[3], ch3[]="Are you ready?", ch4='Q';
2)、int w1, w2[5], w3=0x1234, w4[]={90, 89, 432, 344};
3)、long int d1=1234, d2, d3[10], d4[]={1, 2, 3, 4, 5, 6};

4.4、假设4.3题中三组变量在内存中是连续存放的，且变量ch1的偏移量是20H，试求出其它每个变量的偏移量。

4.5、按下列要求分别写出汇编语言的结构定义，如果没有说明结构分量的类型，请根据日常习惯自行指定其数据类型(可使用已有的结构定义)。

1)、定义日期结构类型Date，其包括：Year(年)、Month(月)和Day(日)；

2)、定义时间结构类型Time，其包括：Hour(小时)、Minute(分)、Second(秒)和centsec(百分之一秒)；

3)、定义结构Peason，其包括：name(姓名，字符，长度为8)、Birth(出生日期，Date)，stature(身高，以厘米为单位的整数)，Address(地址，30个字符)

4.6、试求出4.5题各结构中字段的偏移量。

4.7、把下列C语言的结构或结构变量的定义改写为与之等价的汇编语言定义语句。

1)、struct ScreenBuffer {
           char　c1;　　　//字符
           char　attr;　　//字符属性
        } Buffer[2000];

2)、struct FILE {
           int　level, flags;
           char　fd, hold;
           int　bsize, istemp, token;
          };

3)、struct CELLREC {
           char　attrib;
           union {char　text[201];
                  long　value;
                  struct {long　fvalue;
                         char　formula[201];
                        } f;
                 } v;
          };

4.8、按下列要求分别写出汇编语言的记录定义。

1)、定义标志位寄存器低8位标志位，其相应位用其标志位的符号来命名，未用部分的命名自行确定；

2)、定义一个压缩型的BCD码类型；

3)、把一个字节分成三部分：0~2位、3~5位和6~7位，每部分可独立存取，它们的初值分别为4，5和2；

4)、把一个字分割成：0~1位、2~6位、7~12位，其中第一部分的初值位3，第三部分的初值为25。

4.9、试求出4.8题各记录中字段的宽度和屏蔽属性值。

4.10、假设变量B1的偏移量为12H，试求出它们的属性OFFSET、TYPE、LENGTH和SIZE的值。

B1　DB　　　　"The programming language is easy", 0
W1　DW　　　 10 DUP(1, 2), 1234
D1　DD　　　　20 DUP(0)
P1　PEASON　<>, <>　　　;结构PEASON的定义见4.4.2
U1　UNION1　3 DUP(<>)　　;联合UNION1的定义见4.4.3

4.11、试用三种方法定义字节变量(或字段名)B1和字变量(或字段名)W1，它们共享20个存储单元。

4.12、强制属性操作符会改变变量的定义属性吗？在C语言中存在类似功能的语句吗？若存在，请举例说明。

4.13、假设有题4.10题中的变量定义，试写出符合要求的地址表达式。

1)、取从B1开始的字母'p'和第一个'm'；

2)、访问从W1开始的第3个字单元(从序数1开始计数，第1、第2、……)的值为2的存储单元；

3)、访问从D1开始的第10个双字单元；

4)、访问从P1开始的第2个结构中的字段NO；

5)、访问从U1开始的第3个联合中的字段WW；

4.14、把下列C语言程序中的说明性语句改写成等价的汇编语言语句。

#define　N　200
struct buffer {
      char　len1, len2, buff[N];
} buff;
int Data[N];

4.15、简述数值表达式与地址表达式的主要区别。

4.16、计算下列各数值表达式的值。

1)、12H AND 78H
3)、5467H XOR 12FFH
5)、10 LT 9
7)、1234H+89H SHL 1

 2)、1234 MOD 3 + 23H
4)、'A' OR 20H
6)、4567 / 13 + 10H
8)、8910H SHR 2 AND 506H


第5章 微机CPU的指令系统

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

第5章 微机CPU的指令系统

指令系统确定了CPU所能完成的功能，是用汇编语言进行程序设计的最基本部分。如果不熟悉汇编指令的功能及其有关规定，那么，肯定不能灵活运用汇编语言。所以，本章的内容是学习本课程的重点和难点。

5.1 汇编语言指令格式

为了介绍指令系统中指令的功能，先要清楚汇编语言是如何书写指令的，这就象在学习高级语言程序设计时，要清楚高级语言语句的语义、语法及其相关规定一样。

5.1.1 指令格式

汇编语言的指令格式如下：

指令助忆符 [操作数1 [, 操作数2 [, 操作数3]]]　　　[;注释]

指令助忆符体现该指令的功能，它对应一条二进制编码的机器指令。指令的操作数个数由该指令的确定，可以没有操作数，也可以有一个、二个或三个操作数。绝大多数指令的操作数要显式的写出来，但也有指令的操作数是隐含的，不需要在指令中写出。

当指令含有操作数，并要求在指令中显式地写出来时，则在书写时必须遵守：

指令助忆符和操作数之间要有分隔符，分隔符可以是若干个空格或TAB键；
如果指令含有多个操作数，那么，操作数之间要用逗号"，"分开。

指令后面还可以书写注释内容，不过，要在注释之前书写分号"；"。

5.1.2 了解指令的几个方面

在学习汇编指令时，指令的功能无疑是我们学习和掌握的重点，但要准确、有效地运用这些指令，我们还要熟悉系统对每条指令的一些规定或约束。

归纳起来，对指令还要掌握以下几个方面内容：

、要求指令操作数的寻址方式；
、指令对标志位的影响、标志位对指令的影响；
、指令的执行时间，对可完成同样功能的指令，要选用执行时间短的指令(见附录2)。


5.2 指令系统

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统

5.2、指令系统

指令系统是CPU指令的集合，CPU除了具有计算功能的指令外，还有实现其它功能的指令，也有为某种特殊的应用而增设的指令。

通常，把指令按其功能分成以下几大类：

  • 数据传送指令

  • 标志位操作指令

  • 算术运算指令

  • 逻辑运算指令

  • 移位操作指令

  • 位操作指令

  • 比较运算指令

下面，我们逐一介绍每类指令中的指令。
  • 循环指令
  • 转移指令
  • 条件设置字节指令
  • 字符串操作指令
  • ASCII-BCD码运算调整指令
  • 处理器指令

5.2.1 数据传送指令

数据传送指令又分为：传送指令、交换指令、地址传送指令、堆栈操作指令、转换指令和I/O 指令等。

除了标志位操作指令SAHF和POPF指令外，本类的其它指令都不影响标志位。

1、传送指令MOV(Move Instruction)

传送指令是使用最频繁的指令，它相对于高级语言里的赋值语句。指令的格式如下：

MOV Reg/Mem, Reg/Mem/Imm

其中：Reg—Register(寄存器)，Mem—Memory(存储器)，Imm—Immediate(立即数)，它们可以是8位、16位或32位(特别指出其位数的除外)。在本网络课件的网页中，都将采用上述缩写，此后不再说明。

指令的功能是把源操作数(第二操作数)的值传给目的操作数(第一操作数)。指令执行后，目的操作数的值被改变，而源操作数的值不变。在存储单元是该指令的一个操作数时，该操作数的寻址方式可以是任意一种存储单元寻址方式。

下面列举几组正确的指令例子：

源操作数是寄存器
 | MOV CH, AL | MOV BP, SP | MOV ECX, EBX
 | MOV DS, AX | MOV [BX], CH | MOV [BX+SI], AX
源操作数是存储单元
 | MOV AL, [100H] | MOV BX, ES:[DI] | MOV EDX, [BX]
 | MOV BX, VARW | MOV AX, [BX+SI] | MOV CH, [BX+DI+100H]
 | 其中：VARW是字类型内存变量(下同)。
源操作数是立即数
 | MOV AL, 89H | MOV BX, -100H | MOV EDX, 12345678H
 | MOV VARW, 200H | MOV [BX], 2345H | MOV [BX+DI], 1234H

在汇编语言中，主要的数据传送方式如图5.1所示。虽然一条MOV指令能实现其中大多数的数据传送方式，但也存在MOV指令不能实现的传送方式。

图5.1 MOV指令数据传送示意图

对MOV指令有以下几条具体规定，其中有些规定对其它指令也同样有效。

1)、两个操作数的数据类型要相同，要同为8位、16位或32位；如：MOV BL, AX等是不正确的；
2)、两个操作数不能同时为段寄存器，如：MOV ES, DS等；

3)、代码段寄存器CS不能为目的操作数，但可作为源操作数，如：指令MOV CS, AX等不正确，但指令MOV AX, CS等是正确的；

4)、立即数不能直接传给段寄存器，如：MOV DS, 100H等；
5)、立即数不能作为目的操作数，如：MOV 100H, AX等；
6)、指令指针IP，不能作为MOV指令的操作数；
7)、两个操作数不能同时为存储单元，如：MOV VARA, VARB等，其中VARA和VARB是同数据类型的内存变量。

对于规定2、4和7，我们可以用通用寄存器作为中转来达到最终目的。表5.1列举一个可行的解决方案，尽供参考。读者可考虑用其它办法来完成同样的功能。

表5.1 MOV指令的变通方法

功能描述 | 不正确的指令 | 可选的解决方法
把DS的值传送给ES | MOV ES, DS | MOV AX, DS MOV ES, AX
把100H传给DS | MOV DS, 100H | MOV AX, 100H MOV DS, AX
把字变量VARB的值传送给字变量VARA | MOV VARA, VARB | MOV AX, VARB MOV VARA, AX

对于情况1：不同位数数据之间的传送问题，在80386及其以后的CPU中，增加一组新的指令——传送－填充指令，它可把位数少的源操作数传送给位数多的目的操作数，多出的部分按指令的规定进行填充。


5.2.1-传送－填充指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 传送—填充指令

2、传送—填充指令(Move-and-Fill Instruction)

传送—填充指令是把位数短的源操作数传送给位数长的目的操作数。指令格式如下：

MOVSX/MOVZX Reg/Mem, Reg/Mem/Imm　　　　　;80386+

其中：80386+表示80386及其之后的CPU，其它类似符号含义类同，不再说明。

指令的主要功能和限制与MOV指令类似，不同之处是：在传送时，对目的操作数的高位进行填充。根据其填充方式，又分为：符号填充和零填充。

传送—填充指令的功能如图5.2所示。

(a). MOVSX的执行效果

(b). MOVZX的执行效果

图5.2 传送—填充指令执行过程示意图

、符号填充指令MOVSX(Move with Sign-Extend)

MOVSX的填充方式是：用源操作数的符号位来填充目的操作数的高位数据位。

、零填充指令MOVZX(Move with Zero-Extend)

MOVZX的填充方式是：恒用0来填充目的操作数的高位数据位。

例5.1 已知：AL=87H，指令MOVSX CX, AL，MOVZX DX, AL执行后，问CX和DX的值是什么？
解：根据传送－填充指令的填充方式可知：

指令MOVSX CX, AL执行后，(CX)=0FF87H，指令MOVZX DX, AL执行后，(DX)=0087H。

从上例可看出，两条指令的源操作数完全一样，但因为它们的填充方式不同，所得到的结果而就不同。

试比较下列指令，分析它们执行结果的相同和不同之处：

MOV　AX, 87H　　　MOVSX　AX, 87H　　　MOVZX　AX, 87H

3、交换指令XCHG(Exchange Instruction)

交换指令XCHG是两个寄存器，寄存器和内存变量之间内容的交换指令，两个操作数的数据类型要相同。其指令格式如下：

XCHG Reg/Mem, Reg/Mem

该指令的功能和MOV指令不同，后者是一个操作数的内容被修改，而前者是两个操作数都会发生改变。寄存器不能是段寄存器，两个操作数也不能同时为内存变量。

XCHG指令的功能如图5.3所示。

图5.3 XCHG指令的执行功能示意图

例5.2 已知：AX=5678H，BX=1234H，指令XCHG AX, BX执行后，AX和BX的值是什么？
解：这是两个寄存器内容进行交换，指令执行后，有：(AX)=1234H，(BX)=5678H。


5.2.1-取有效地址指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 取有效地址指令

4、取有效地址指令LEA(Load Effective Address)

指令LEA是把一个内存变量的有效地址送给指定的寄存器。其指令格式如下：

LEA　Reg, Mem

该指令通常用来对指针或变址寄存器BX、DI或SI等置初值之用。其功能如右图所示。

例如：

图5.4 LEA指令的功能示意图

…
BUFFER | DB 100 DUP(?)
…
LEA | BX, BUFFER ;把字节变量BUFFER在数据段内的偏移量送给BX
…

问题：指令“LEA BX BUFFER”和“MOV BX, OFFSET BUFFER”的执行效果是一样的吗？指令“LEA BX,[BX+200]”和“MOV BX,OFFSET [BX+200]”二者都正确吗？

5、取段寄存器指令(Load Segment Instruction)

该组指令的功能是把内存单元的一个“低字”传送给指令中指定的16位寄存器，把随后的一个“高字”传给相应的段寄存器(DS、ES、FS、GS和SS)。其指令格式如下：

LDS/LES/LFS/LGS/LSS　Reg, Mem

指令LDS(Load Data Segment Register)和LES(Load Extra Segment Register)在8086CPU中就存在，而LFS和LGS(Load Extra Segment Register)、LSS(Load Stack Segment Register)是80386及其以后CPU中才有的指令。

若Reg是16位寄存器，那么，Men必须是32位指针；若Reg是32位寄存器，那么，Men必须是48位指针，其低32位给指令中指定的寄存器，高16位给指令中的段寄存器。指令的执行结果如图5.5所示。

(a) 32位指针

(b) 48位指针

图5.5、LDS指令的执行步骤示意图

例如：
 | …
 | POINTER DD 12345678H
 | …
 | LDS | BX, POINTER
 | …
指令的执行结果如图5.5所示。各寄存器的内容分别为：(BX)=5678H，(DS)=1234H。

下面控件是学习和掌握MOV、MOVSX/MOVZX、XCHG、LEA、LDS/LES/LFS/LGS/LSS指令的，它可检查用户输入这些指令的合法性，并对合法的指令显示其执行的结果。

注意：如果指令中含有表示内存单元的寻址方式，那么其控件中的"内存单元的类型"即表示该指令中内存单元的数据类型。


5.2.1-堆栈操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 取有效地址指令

6、堆栈操作指令(Stack Operation Instruction)

堆栈是一个重要的数据结构，它具有“先进后出”的特点，通常用来保存程序的返回地址。它主要有两大类操作：进栈操作和出栈操作。

1)、进栈操作

、PUSH(Push Word or Doubleword onto Stack)

指令格式：PUSH　Reg/Mem
PUSH　Imm　　　　　　　　　　;80286+

  • 一个字进栈，系统自动完成两步操作：SP←SP-2，(SP)←操作数；

  • 一个双字进栈，系统自动完成两步操作：ESP←ESP-4，(ESP)←操作数。

、PUSHA(Push All General Registers)

指令格式：PUSHA　　　　　　　;80286+

其功能是依次把寄存器AX、CX、DX、BX、SP、BP、SI和DI等压栈。

、PUSHAD(Push All 32-bit General Registers)

指令格式：PUSHAD　　　　　　　;80386+

其功能是把寄存器EAX、ECX、EDX、EBX、ESP、EBP、ESI和EDI等压栈。

2)、出栈操作

、POP(Pop Word or Doubleword off Stack)

指令格式：POP　Reg/Mem

  • 弹出一个字，系统自动完成两步操作：操作数←(SP)，SP←SP-2；

  • 弹出一个双字，系统自动完成两步操作：操作数←(ESP)，ESP←ESP-4。

、POPA(Pop All General Registers)

指令格式：POPA　　　　　　　;80286+

其功能是依次把寄存器DI、SI、BP、SP、BX、DX、CX和AX等弹出栈。其实，程序员不用记住它们的具体顺序，只要与指令PUSHA对称使用就可以了。

、POPAD(Pop All 32-bit General Registers)

指令格式：POPAD　　　　　　;80386+

其功能是依次把寄存器EDI、ESI、EBP、ESP、EBX、EDX、ECX和EAX等弹出栈，它与PUSHAD对称使用即可。

7、转换指令XLAT(Translate Instruction)

转换指令有两个隐含操作数BX和AL。指令格式如下：

XLAT/XLATB

其功能是把BX的值作为内存字节数组首地址、下标为AL的数组元素的值传送给AL。其功能描述的表达式是：AL←BX[AL]，其功能示意图如图5.6所示。

图5.6 XLAT/XLATB指令的功能示意图

8、I/O指令

有关I/O指令将在第8.1.2节——I/O指令——中介绍，在此从略。


5.2.2 标志位操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 标志位操作指令

5.2.2 标志位操作指令

标志位操作指令是一组对标志位置位、复位、保存和恢复等操作的指令。

1、进位CF操作指令

、清进位指令CLC(Clear Carry Flag)：CF←0
、置进位指令STC(Set Carry Flag)：CF←1
、进位取反指令CMC(Complement Carry Flag)：CF←not CF

2、方向位DF操作指令

、清方向位指令CLD(Clear Direction Flag)：DF←0
、置方向位指令STD(Set Direction Flag)：DF←1

3、中断允许位IF操作指令

、清中断允许位指令CLI(Clear Interrupt Flag)：IF←0
其功能是不允许可屏蔽的外部中断来中断其后程序段的执行。

、置中断允许位指令STI(Set Interrupt Flag)：IF←1
其功能是恢复可屏蔽的外部中断的中断响应功能，通常是与CLI成对使用的。

4、取标志位操作指令

、LAHF(Load AH from Flags)：AH←Flags的低8位
、SAHF(Store AH in Flags)：Flags的低8位←AH

5、标志位堆栈操作指令

、PUSHF/PUSHFD(Push Flags onto Stack)：把16位/32位标志寄存器进栈；
、POPF/POPFD(Pop Flags off Stack)：把16位/32位标志寄存器出栈；

6、逻辑操作指令的小结

下面是学习标志位指令的控件，浏览者可以运用此类指令，观看标志寄存器的相应变化。


5.2.3 算术运算指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 算术运算指令

5.2.3 算术运算指令

算术运算指令是反映CPU计算能力的一组指令，也是编程时经常使用的一组指令。它包括：加、减、乘、除及其相关的辅助指令。

该组指令的操作数可以是8位、16位和32位(80386+)。当存储单元是该类指令的操作数时，该操作数的寻址方式可以是任意一种存储单元寻址方式。

1、加法指令

、加法指令ADD(ADD Binary Numbers Instruction)

指令的格式：ADD Reg/Mem, Reg/Mem/Imm
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能是把源操作数的值加到目的操作数中。

、带进位加指令ADC(ADD With Carry Instruction)

指令的格式：ADC Reg/Mem, Reg/Mem/Imm
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能是把源操作数和进位标志位CF的值(0/1)一起加到目的操作数中。

、加1指令INC(Increment by 1 Instruction)

指令的格式：INC Reg/Mem
受影响的标志位：AF、OF、PF、SF和ZF，不影响CF
指令的功能是把操作数的值加1。

、交换加指令XADD(Exchange and Add)

指令的格式：XADD Reg/Mem, Reg　　　　　　;80486+
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能是先交换两个操作数的值，再进行算术“加”法操作。

例5.3 已知有二个32位数d1和d2(用数据类型DD说明)，编写程序片段把d2的值加到d1中。

解：32位数d1和d2在内存中如下所示。
…… 　 　 　 　 …… 　 　 　 　 ……
方法1：用16位寄存器编写程序
MOV | AX, word ptr d1 | ;由于d1是双字类型，必须使用强制类型说明符。以下同。
MOV | DX, word ptr d1+2 | ;(DX,AX)构成一个32位数据
ADD | AX, word ptr d2 | ;低字相加
ADC | DX, word ptr d2+2 | ;高字相加。在低字相加时，有可能会产生“进位”
MOV | word ptr d1, AX | ;低字送给d1的低字
MOV | word ptr d1+2, DX | ;高字送给d1的高字
方法2：用32位寄存器编写程序
MOV | EAX, d1
ADD | EAX, d2
MOV | d1, EAX

从上面两段程序不难看出：用32位寄存器来处理32位数据显得简单、明了，而16位微机虽然也能处理32位数据，但做起来就要复杂一些。

下面是学习和掌握加法类指令的控件，可模拟执行ADD、ADC、INC、XADD、CLC、STC和CMC等指令。用鼠标左键单击寄存器列表框中指定的寄存器，则可修改其值。后面其它控件的有关操作与此相一致，不再说明。


5.2.3-2减法指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 减法指令

2、减法指令

、减法指令SUB(Subtract Binary Values Instruction)

指令的格式：SUB Reg/Mem, Reg/Mem/Imm
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能是从目的操作数中减去源操作数。

、带借位减SBB(Subtract with Borrow Instruction)

指令的格式：SBB Reg/Mem, Reg/Mem/Imm
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能是把源操作数和标志位CF的值从目的操作数中一起减去。

、减1指令DEC(Decrement by 1 Instruction)

指令的格式：DEC Reg/Mem
受影响的标志位：AF、OF、PF、SF和ZF，不影响CF
指令的功能是把操作数的值减去1。

、求补指令NEG(Negate Instruction)

指令的格式：NEG Reg/Mem
受影响的标志位：AF、CF、OF、PF、SF和ZF
指令的功能：操作数＝0－操作数，即改变操作数的正负号。

例5.4 已知有二个32位数d1和d2，编写程序片段从d1中减去d2的值。

解：
 | 方法1：用16位寄存器编写程序
 | MOV | AX, word ptr d1 | ;取低字
 | MOV | DX, word ptr d1+2 | ;取高字，(DX,AX)构成一个32位数据
 | SUB | AX, word ptr d2 | ;低字相减
 | SBB | DX, word ptr d2+2 | ;高字相减。在低字相减时，有可能会产生“借位”
 | MOV | word ptr d1, AX | ;低字送给d1的低字
 | MOV | word ptr d1+2, DX | ;高字送给d1的高字
 | 方法2：用32位寄存器编写程序
 | MOV | EAX, d1
 | SUB | EAX, d2
 | MOV | d1, EAX

下面是学习和掌握减法类指令的控件，可模拟执行SUB、SBB、DEC、NEG、CLC、STC和CMC等指令。


5.2.3-3乘法指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 乘法指令

3、乘法指令

计算机的乘法指令分为无符号乘法指令和有符号乘法指令，它们的唯一区别就在于：数据的最高位是作为“数值”参与运算，还是作为“符号位”参与运算。

乘法指令的被乘数都是隐含操作数，乘数在指令中显式地写出来。CPU会根据乘数是8位、16位，还是32位操作数，来自动选用被乘数：AL、AX或EAX。

指令的功能是把显式操作数和隐含操作数相乘，并把乘积存入相应的寄存器中。

、无符号数乘法指令MUL(Unsigned Multiply Instruction)

指令的格式：MUL Reg/Mem
受影响的标志位：CF和OF(AF、PF、SF和ZF无定义)
指令的功能是把显式操作数和隐含操作数(都作为无符号数)相乘，所得的乘积按表5.2的对应关系存放。

表5.2 乘法指令中乘数、被乘数和乘积的对应关系

乘数位数 | 隐含的被乘数 | 乘积的存放位置 | 举例
8位 | AL | AX | MUL BL
16位 | AX | DX-AX | MUL BX
32位 | EAX | EDX-EAX | MUL ECX

、有符号数乘法指令IMUL(Signed Integer Multiply Instruction)

指令的格式： | IMUL | Reg/Mem
 | IMUL | Reg, Imm | ;80286+
 | IMUL | Reg, Reg, Imm | ;80286+
 | IMUL | Reg, Reg/Mem | ;80386+

受影响的标志位：CF和OF(AF、PF、SF和ZF无定义)

1)、指令格式1——该指令的功能是把显式操作数和隐含操作数相乘，所得的乘积按表5.2的对应关系存放。

2)、指令格式2——其寄存器必须是16位/32位通用寄存器，其计算方式为：

Reg ← Reg × Imm

3)、指令格式3——其寄存器只能是16位通用寄存器，其计算方式为：

Reg1 ← Reg2×Imm 或 Reg1 ← Mem×Imm

4)、指令格式4——其寄存器必须是16位/32位通用寄存器，其计算方式为：

Reg1 ← Reg1×Reg2 或 Reg1 ← Reg1×Mem

在指令格式2~4中，各操作数的位数要一致。如果乘积超过目标寄存器所能存储的范围，则系统将置溢出标志OF为1。

下面是学习和掌握乘法类指令的控件，可模拟执行MUL和IMUL等指令。


5.2.3-4 除法指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 除法指令

4、除法指令

除法指令的被除数是隐含操作数，除数在指令中显式地写出来。CPU会根据除数是8位、16位，还是32位，来自动选用被除数AX、DX-AX，还是EDX-EAX。

除法指令功能是用显式操作数去除隐含操作数，可得到商和余数。当除数为0，或商超出数据类型所能表示的范围时，系统会自动产生0号中断。

、无符号数除法指令DIV(Unsigned Divide Instruction)

指令的格式：DIV Reg/Mem
指令的功能是用显式操作数去除隐含操作数(都作为无符号数)，所得商和余数按表5.3的对应关系存放。指令对标志位的影响无定义。

、有符号数除法指令IDIV(Signed Integer Divide Instruction)

指令的格式：IDIV Reg/Mem
受影响的标志位：AF、CF、OF、PF、SF和ZF

指令的功能是用显式操作数去除隐含操作数(都作为有符号数)，所得商和余数的对应关系见表5.3。

表5.3 除法指令除数、被除数、商和余数的对应关系

除数位数 | 隐含的被除数 | 商 | 余数 | 举例
8位 | AX | AL | AH | DIV BH
16位 | DX-AX | AX | DX | DIV BX
32位 | EDX-EAX | EAX | EDX | DIV ECX


5.2.3 类型转换指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 类型转换指令

5、类型转换指令

在作有符号除法时，有时需要把短位数的被除数转换成位数更长的数据类型。比如，要用BL中的数据去除AL，但根据除法指令的规定：除数是8位，则被除数必须是AX，于是就涉及到AH的取值问题。

为了方便说明，假设：(AH)=1H，(AL)=90H=-112D，(BL)=10H。

1)、在作除法运算前，必须处理AH的原有内容

假设在作除法时，不管AH中的值，这时，(AH、AL)/BL的商是19H，但我们知道：AL/BL的商应是-7，这就导致：计算结果不是所预期的结果，所以，在作除法运算前，程序员必须要处理AH中的值。

2)、作无符号数除法时

可强置AH的值为0，于是，可得到正确的结果。

3)、作有符号数除法时

如果强置AH为0，则AX=0090H，这时，AX/BL的商为9，显然结果也不正确。

如果把AL的符号位1，扩展到AH中，得：AX=0FF90H=-112D，这时，AX/BL的商就是我们所要的正确结果。

综上所述，因为在进行有符号数除法时存在隐含操作数数据类型转换的问题，所以，系统提供了四条数据类型转换指令：CBW、CWD、CWDE和CDQ。

、字节转换为字指令CBW(Convent Byte to Word)

指令的格式：CBW
该指令的隐含操作数为AH和AL。其功能是用AL的符号位去填充AH，即：当AL为正数，则AH=0，否则，AH=0FFH。
指令的执行不影响任何标志位。

、字转换为双字指令CWD(Convent Word to Doubleword)

指令的格式：CWD
该指令的隐含操作数为DX和AX，其功能是用AX的符号位去填充DX。指令的执行不影响任何标志位。

、字转换为扩展的双字指令CWDE(Convent Word to Extended Doubleword)

指令的格式：CWDE　　　　　　　　;80386+
该指令的隐含操作数为DX和AX，其功能是用AX的符号位填充EAX的高字位。指令的执行不影响任何标志位。

、双字转换为四字指令CDQ(Convent Doubleword to Quadword)

指令的格式：CDQ　　　　　　　　　;80386+
该指令的隐含操作数为EDX和EAX，指令的功能是用EAX的符号位填充EDX。指令的执行不影响任何标志位。

下面是学习和掌握除法类指令的控件，可模拟执行DIV、IDIV、CBW、CWD、CWDE和CDQ等指令。

例5.5 编写程序段，完成下面计算公式，并把所得的商和余数分别存入X和Y中(其中：A，B，C，X和Y都是有符号的字变量)。

(C - 120 + A*B) / C

解：
 | …
 | A | DW ?
 | B | DW ?
 | C | DW ?
 | X | DW ?
 | Y | DW ?
 | …
 | MOV | AX, C
 | SUB | AX, 120D | ;书写指令“ADD AX, -120D”也可以
 | CWD
 | MOV | CX, DX
 | MOV | BX, AX | ;(CX, BX)←(DX, AX)，调度寄存器，为作乘法准备必要的寄存器
 | MOV | AX, A
 | IMUL | B | ;(DX, AX)←A*B
 | ADD | AX, BX | ;计算32位二进制之和，为作除法作准备
 | ADC | DX, CX
 | IDIV | C | ;AX是商，DX是余数
 | MOV | X, AX | ;分别保存商和余数到指定的字变量单元里
 | MOV | Y, DX
 | …


5.2.4 逻辑运算指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 逻辑运算指令

5.2.4 逻辑运算指令

逻辑运算指令是另一组重要的指令，它包括：逻辑与(AND)、逻辑或(OR)、逻辑非(NOT)和异或指令(XOR)，逻辑运算指令也是经常使用的指令。

1、逻辑与操作指令AND(Logical AND Instruction)

指令的格式：AND　Reg/Mem, Reg/Mem/Imm
受影响的标志位：CF(0)、OF(0)、PF、SF和ZF(AF无定义)
指令的功能是把源操作数中的每位二进制与目的操作数中的相应二进制进行逻辑“与操作”，操作结果存入目标操作数中。

例5.6 已知(BH)=67H，要求把其的第0、1和5位置为0。

解：可以构造一个立即数，其第0、1和5位的值为0，其它位的值为1，该立即数即为：0DCH或11011100B，然后用指令"AND BH, 0DCH"来实现此功能。

其计算过程如右图所示。

2、逻辑或操作指令OR(Logical OR Instruction)

指令的格式：OR　Reg/Mem, Reg/Mem/Imm
受影响的标志位：CF(0)、OF(0)、PF、SF和ZF(AF无定义)
指令的功能是把源操作数中的每位二进制与目的操作数中的相应二进制进行逻辑"或操作"，操作结果存入目标操作数中。

例5.7 已知(BL)=46H，要求把其的第1、3、4和6位置为1。

解：构造一个立即数，使其第1、3、4和6位的值为1，其它位的值为0，该立即数即为：5AH或01011010B，然后用指令"OR BL, 5AH"来实现此功能。

其计算过程如右图所示。

3、逻辑非操作指令NOT(Logical NOT Instruction)

指令的格式：NOT　Reg/Mem
其功能是把操作数中的每位变反，即：1←0，0←1。指令的执行不影响任何标志位。

例5.8 已知(AL)=46H，执行指令“NOT　AL”后，AL的值是什么？

解：执行该指令后，(AL)=0B9H。其计算过程如下所示。

4、逻辑异或操作指令XOR(Exclusive OR Instruction)

指令的格式：XOR　Reg/Mem, Reg/Mem/Imm
受影响的标志位：CF(0)、OF(0)、PF、SF和ZF(AF无定义)
指令的功能是把源操作数中的每位二进制与目的操作数中的相应二进制进行逻辑"异或操作"，操作结果存入目标操作数中。

例5.9 已知(AH)=46H，要求把其的第0、2、5和7位的二进制值变反。

解：构造一个立即数，使其第0、2、5和7位的值为1，其它位的值为0，该立即数即为：0A5H或10100101B，然后再用指令"XOR AH, 0A5H"来实现此功能。

其计算过程如右图所示。

5、逻辑操作指令的小结

下面是学习和掌握逻辑类指令的控件，可模拟执行AND、OR、NOT和XOR等指令。


5.2.5 移位操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 移位操作指令

5.2.5 移位操作指令

移位操作指令是一组经常使用的指令，它包括算术移位、逻辑移位、双精度移位、循环移位和带进位的循环移位等五大类。

移位指令都有指定移动二进制位数的操作数，该操作数可以是立即数或CL的值。在8086中，该立即数只能为1，但在其后的CPU中，该立即数可以是1··31之内的数。

1、算术移位指令

算术移位指令有：算术左移SAL(Shift Algebraic Left)和算术右移SAR(Shift Algebraic Right)。它们的指令格式如下：

SAL/SAR　Reg/Mem, CL/Imm

受影响的标志位：CF、OF、PF、SF和ZF(AF无定义)。

算术移位指令的功能描述如下，具体功能下图(a)、(b)所示。

算术左移SAL把目的操作数的低位向高位移，空出的低位补0；
     算术右移SAR把目的操作数的高位向低位移，空出的高位用最高位(符号位)填补。

(a)、SAL | (b)、SAR

例5.10 已知(AH)=12H，(BL)=0A9H，试给出分别用算术左移和右移指令移动1位后，寄存器AH和BL的内容。

解：用算术左移和右移指令移动1位后，寄存器AH和BL的结果如下表所示。

操作数的初值 | 执行的指令 | 执行后操作数的内容
(AH)=12H | SAL AH, 1 | (AH)=24H
(BL)=0A9H | SAL BL, 1 | (BL)=52H
(AH)=12H | SAR AH, 1 | (AH)=09H
(BL)=0A9H | SAR BL, 1 | (BL)=0D4H

下面是学习和理解算术移位指令的控件。它简单、直观地表达了该移位指令的功能，通过它，学习者可准确地掌握计算机系统中该移位指令的含义。

在该控件中，操作者可随机生成第一操作数，也可自行输入之。为了便于比较，在执行指令前，把原操作数的内容存入“操作前的数据”中。

思考题：下面有两组指令序列，问每组指令执行后，寄存器AX的不会变化吗？

SAL　AX, 1
SAR　AX, 1

或

 SAR　AX, 1
SAL　AX, 1


5.2.5 逻辑移位指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 移位操作指令

2、逻辑移位指令

此组指令有：逻辑左移SHL(Shift Logical Left)和逻辑右移SHR(Shift Logical Right)。它们的指令格式如下：

SHL/SHR　Reg/Mem, CL/Imm

受影响的标志位：CF、OF、PF、SF和ZF(AF无定义)。

逻辑左移/右移指令只有它们的移位方向不同，移位后空出的位都补0。它们的具体功能下图(a)、(b)所示。

(a)、SHL | (b)、SHR

例5.11 已知(AH)=12H，(BL)=0A9H，试给出分别用逻辑左移和右移指令移动1位后，寄存器AH和BL的内容。

解：用算术左移和右移指令移动1位后，寄存器AH和BL的结果如下表所示。

操作数的初值 | 执行的指令 | 执行后操作数的内容
(AH)=12H | SHL AH, 1 | (AH)=24H
(BL)=0A9H | SHL BL, 1 | (BL)=52H
(AH)=12H | SHR AH, 1 | (AH)=09H
(BL)=0A9H | SHR BL, 1 | (BL)=54H

学习和理解逻辑移位指令的控件。


5.2.5 双精度移位指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 双精度移位指令

3、双精度移位指令

此组指令有：双精度左移SHLD(Shift Left Double)和双精度右移SHRD(Shift Right Double)。它们都是具有三个操作数的指令，其指令的格式如下：

SHLD/SHRD Reg/Mem, Reg, CL/Imm　　　　;80386+

其中：第一操作数是一个16位/32位的寄存器或存储单元；第二操作数(与前者具有相同位数)一定是寄存器；第三操作数是移动的位数，它可由CL或一个立即数来确定。

在执行SHLD指令时，第一操作数向左移n位，其“空出”的低位由第二操作数的高n位来填补，但第二操作数自己不移动、不改变。

在执行SHRD指令时，第一操作数向右移n位，其“空出”的高位由第二操作数的低n位来填补，但第二操作数自己也不移动、不改变。

SHLD和SHRD指令的移位功能示意图如图5.8所示。

受影响的标志位：CF、OF、PF、SF和ZF(AF无定义)

(a)、SHLD | (b)、SHRD

图5.8 双精度移位指令操作示意图

下面是几个双精度移位的例子及其执行结果。

双精度移位指令 | 指令操作数的初值 | 指令执行后的结果
SHLD AX, BX, 1 | (AX)=1234H，(BX)=8765H | (AX)=2469H
SHLD AX, BX, 3 | (AX)=1234H，(BX)=8765H | (AX)=91A4H
SHRD AX, BX, 2 | (AX)=1234H，(BX)=8765H | (AX)=448DH
SHRD AX, BX, 4 | (AX)=1234H，(BX)=8765H | (AX)=5123H

学习和理解双精度移位指令的控件。


5.2.5 循环移位指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 循环移位指令

4、循环移位指令

循环移位指令有：循环左移ROL(Rotate Left)和循环右移ROR(Rotate Right)。

指令的格式：ROL/ROR　Reg/Mem, CL/Imm

受影响的标志位：CF和OF

循环左移/右移指令只是移位方向不同，它们移出的位不仅要进入CF，而且还要填补空出的位。具体功能如下图(a)、(b)所示。

(a)、ROL | (b)、ROR

下面是几个循环移位的例子及其执行结果。

循环移位指令 | 指令操作数的初值 | 指令执行后的结果
ROL AX, 1 | (AX)=6789H | (AX)=0CF12H
ROL AX, 3 | (AX)=6789H | (AX)=3C4BH
ROR AX, 2 | (AX)=6789H | (AX)=59E2H
ROR AX, 4 | (AX)=6789H | (AX)=9678H

学习和理解不带进位的循环移位指令的控件。


5.2.5 带进位的循环移位指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 带进位的循环移位指令

5、带进位的循环移位指令

带进位的循环移位指令有：带进位的循环左移RCL(Rotate Left Through Carry)和带进位的循环右移RCR(Rotate Right)。

指令的格式：RCL/RCR　Reg/Mem, CL/Imm

受影响的标志位：CF和OF

带进位的循环左移／右移指令只有移位的方向不同，它们都用原CF的值填补空出的位，移出的位再进入CF。具体功能如下图(a)、(b)所示。

(a)、RCL | (b)、RCR

下面是几个带进位循环移位的例子及其执行结果。

双精度移动指令 | 指令操作数的初值 | 指令执行后的结果
RCL AX, 1 | CF=0，(AX)=0ABCDH | (AX)=579AH
RCL AX, 1 | CF=1，(AX)=0ABCDH | (AX)=579BH
RCR AX, 2 | CF=0，(AX)=0ABCDH | (AX)=AAF3H
RCR AX, 2 | CF=1，(AX)=0ABCDH | (AX)=EAF3H

例5.12 编写指令序列把由DX和AX组成的32位二进制算术左移、循环左移1位。

解：

(DX,AX)算术左移1位指令序列 | (DX,AX)循环左移1位指令序列
SHL AX, 1 RCL DX, 1 | SHLD DX, AX, 1 RCL AX, 1

学习和理解带进位的循环移位指令的控件。

6、移位指令的学习和理解

下面的控件是把各种移位指令的模拟功能组合在一起。它不仅简单、直观地表达了各种移位指令的功能，而且能通过对照，理解它们之间的区别。该控件还结合了其它指令(CLC、STC和CMC)来增加指令的组合变化。


5.2.6 位操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 位操作指令

5.2.6 位操作指令

1、位扫描指令(Bit Scan Instruction)

指令的格式：BSF/BSR　Reg, Reg/Mem　　　　　;80386+

受影响的标志位：ZF

位扫描指令是在第二个操作数中找第一个“1”的位置。如果找到，则该“1”的位置保存在第一操作数中，并置标志位ZF为1，否则，置标志位ZF为0。

根据位扫描的方向不同，指令分二种：正向扫描指令和逆向扫描指令。

、正向扫描指令BSF(Bit Scan Forward)从右向左扫描，即：从低位向高位扫描；
、逆向扫描指令BSR(Bit Scan Reverse)从左向右扫描，即：从高位向低位扫描。

(a) BSF

(b) BSR

图5.10 位扫描指令的功能示意图

例如：

MOV　AX, 1234H
BSF　CX, AX　　　　　　　;指令执行后，(CX)=2
BSR　CX, AX　　　　　　　;指令执行后，(CX)=12


5.2.6 位操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 位操作指令

2、位检测指令(Bit Test Instruction)

指令的格式：BT/BTC/BTR/BTS　Reg/Mem, Reg/Imm　　　　;80386+
受影响的标志位：CF

位检测指令是把第一个操作数中某一位的值传送给标志位CF，具体的哪一位由指令的第二操作数来确定。

根据指令中对具体位的处理不同，又分一下几种指令：

BT：把指定的位传送给CF；
BTC：把指定的位传送给CF后，还使该位变反；
BTR：把指定的位传送给CF后，还使该位变为0；
BTS：把指定的位传送给CF后，还使该位变为1；

图5.11 位检测指令的功能示意图

例如：假设(AX)=1234H，分别执行下面指令。

BT　　AX, 2　　　　　　;指令执行后，CF=1，(AX)=1234h
BTC　AX, 6　　　　　　 ;指令执行后，CF=0，(AX)=1274h
BTR　AX, 10　　　　　　;指令执行后，CF=0，(AX)=1234h
BTS　AX, 14　　　　　　;指令执行后，CF=0，(AX)=5234h

3、检测位指令TEST(Test Bits Instruction)

检测位指令是把二个操作数进行逻辑“与”操作，并根据运算结果设置相应的标志位，但并不保存该运算结果，所以，不会改变指令中的操作数。在该指令后，通常用JE、JNE、JZ和JNZ等条件转移指令。

指令的格式：TEST Reg/Mem, Reg/Mem/Imm

受影响的标志位：CF(0)、OF(0)、PF、SF和ZF(AF无定义)

例如：

TEST　AX, 1　　　　　　　;测试AX的第0位
TEST　CL, 10101B　　　　;测试CL的第0、2、4位

下面是学习和掌握乘法类指令的控件，可模拟执行BSF、BSR、BT、BTC、BTR、BTS和TEST等指令。


5.2.7 比较运算指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 比较运算指令

5.2.7 比较运算指令

在程序中，我们要时常根据某个变量或表达式的取值去执行不同指令，从而使程序表现出有不同的功能。为了配合这样的操作，在CPU的指令系统中提供了各种不同的比较指令。通过这些比较指令的执行来改变有关标志位，为进行条件转移提供依据。

1、比较指令CMP(Compare Instruction)

指令的格式：CMP　Reg/Mem, Reg/Mem/Imm

受影响的标志位：AF、CF、OF、PF、SF和ZF

指令的功能：用第二个操作数去减第一个操作数，并根据所得的差设置有关标志位，为随后的条件转移指令提供条件。但并不保存该差，所以，不会改变指令中的操作数。

2、比较交换指令(Compare And Exchange Instruction)

在数据传送类指令中，我们介绍了交换指令XCHG，它不管二个操作数的值是什么，都无条件地进行交换。而比较交换指令，是先进行比较，再根据比较的结果决定是否进行操作数的交换操作。

比较交换指令的功能：当二个操作数相等时，置标志位ZF为1；否则，把第一操作数的值赋给第二操作数，并置标志位ZF为0。

、8位/16位/32位比较交换指令

指令的格式：CMPXCHG　Reg/Mem, AL/AX/EAX　　　　;80486+
受影响的标志位：AF、CF、OF、PF、SF和ZF
MASM 6.11中指令的描述与此不同，它没有限定第二操作数的要求。

、64位比较交换指令

该指令只有一个操作数，第二个操作数EDX:EAX是隐含的。

指令的格式：CMPXCHG8B　Reg/Mem　　　　　　;Pentium+
受影响的标志位：ZF

例如：假设(AX)=1234H，(BX)=1234H，(CX)=4321H。

CMPXCHG　BX, AX　　　　;指令执行后，ZF=1
CMPXCHG　CX, AX　　　　;指令执行后，ZF=0，(AX)=4321H，CX的值不变

3、字符串比较指令(Compare String Instruction)

参见后面第5.2.11节——字符串操作类指令——的叙述。


5.2.8 循环指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 循环指令

5.2.8 循环指令

循环结构是程序的三大结构之一。为了方便构成循环结构，汇编语言提供了多种循环指令，这些循环指令的循环次数都是保存在计数器CX或ECX中。除了CX或ECX可以决定循环是否结束外，有的循环指令还可由标志位ZF来决定是否结束循环。

在高级语言中，循环计数器可以递增，也可递减，但汇编语言中，CX或ECX只能递减，所以，循环计数器只能从大到小。在程序中，必须先把循环次数赋给循环计数器。

汇编语言的循环指令都是放在循环体的下面，在循环时，首先执行一次循环体，然后把循环计数器CX或ECX减1。当循环终止条件达到满足时，该循环指令下面的指令将是下一条被执行的指令，否则，程序将向上转到循环体的第一条指令。

在循环未终止，而向上转移时，规定：该转移只能是一个短转移，即偏移量不能超过128，也就是说循环体中所有指令码的字节数之和不能超过128。如果循环体过大，可以用后面介绍的“转移指令”来构造循环结构。

循环指令本身的执行不影响任何标志位。

1、循环指令(Loop Until Complete)

循环指令LOOP的一般格式： LOOP 标号 LOOPW 标号 ;CX作为循环计数器，80386+ LOOPD 标号 ;ECX作为循环计数器，80386+ 循环指令的功能描述： |  | 图5.12 循环指令LOOP的功能示意图
 | (CX)=(CX)-1或(ECX)=(ECX)-1；
 | 如果(CX)≠0或(ECX)≠0，转向“标号”所指向的指令，否则，终止循环，执行该指令下面的指令。

例5.13 编写一段程序，求1+2+…+1000之和，并把结果存入AX中。

解：
 | 方法1：因为计数器CX只能递减，所以，可把求和式子改变为：1000+999+…+2+1。
 |  | …
 |  | XOR | AX, AX
 |  | MOV | CX, 1000D
 | again: | ADD | AX, CX | ;计算过程:1000+999+…+2+1
 |  | LOOP | again
 |  | …
 | 方法2：不用循环计数器进行累加，求和式子仍为：1+2+…+999+1000。
 |  | …
 |  | XOR | AX, AX
 |  | MOV | CX, 1000D
 |  | MOV | BX, 1
 | again: | ADD | AX, BX | ;计算过程:1+2+…+999+1000
 |  | INC | BX
 |  | LOOP | again
 |  | …

从程序段的效果来看：方法1要比方法2好。


5.2.8-循环指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 循环指令

2、相等或为零循环指令(Loop While Equal or Loop While Zero)

相等或为零循环指令的一般格式：

LOOPE/LOOPZ　标号
LOOPEW/LOOPZW　标号　　;CX作为循环计数器，80386+
LOOPED/LOOPZD　标号　　 ;ECX作为循环计数器，80386+

这是一组有条件循环指令，它们除了要受CX或ECX的影响外，还要受标志位ZF的影响。其具体规定如下：

(1)、(CX)＝(CX)-1或(ECX)＝(ECX)-1；　(不改变任何标志位)

(2)、如果循环计数器≠0且ZF=1，则程序转到循环体的第一条指令，否则，程序将执行该循环指令下面的指令。

图5.13 循环指令LOOPE的功能示意图

3、不等或不为零循环指令(Loop While Not Equal or Loop While Not Zero)

不等或不为零循环指令的一般格式：

LOOPNE/LOOPNZ　标号
LOOPNEW/LOOPNZW　标号　 ;CX作为循环计数器，80386+
LOOPNED/LOOPNZD　标号　　;ECX作为循环计数器，80386+

这也是一组有条件循环指令，它们与相等或为零循环指令在循环结束条件上有点不同。其具体规定如下：

(1)、(CX)＝(CX)-1或(ECX)＝(ECX)-1；　(不改变任何标志位)

(2)、如果循环计数器≠0且ZF=0，则程序转到循环体的第一条指令，否则，程序将执行该循环指令下面的指令。

图5.14 循环指令LOOPNE的功能示意图

4、循环计数器为零转指令(Jump if CX/ECX is Zero)

在前面的各类循环指令中，不管循环计数器的初值为何，循环体至少会被执行一次。当循环计数器的初值为0时，通常的理解应是循环体被循环0次，即循环体一次也不被执行。其实不然，循环体不是不被执行，而是会被执行65536次(用CX计数)或4294967296次(几乎是死循环，用ECX计数)。

为了解决指令的执行和常规思维之间差异，指令系统又提供了一条与循环计数器有关的指令——循环计数器为零转指令。该指令一般用于循环的开始处，其指令格式如下：

JCXZ　标号　　　;当CX=0时，则程序转移标号处执行
JECXZ　标号　　 ;当ECX=0时，则程序转移标号处执行，80386+

例5.14 编写一段程序，求1+2+…+k(K≥0)之和，并把结果存入AX中。

解：
 |  | …
 |  | K | DB ? ;变量定义
 |  | …
 |  | XOR | AX, AX
 |  | MOV | CX, K
 |  | JCXZ | next
 | again: | ADD | AX, CX ;计算过程: K+(K-1)+…+2+1
 |  | LOOP | again
 | next: | …

思考题：假设变量K的值为0，并且在循环体的前面没有写指令“JCXZ next”，这时求出的“和”AX的值是什么？


5.2.9 转移指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 转移指令

5.2.9 转移指令

转移指令是汇编语言程序员经常使用的一组指令。在高级语言中，时常有“尽量不要使用转移语句”的劝告，但如果在汇编语言的程序中也尽量不用转移语句，那么该程序要么无法编写，要么没有多少功能，所以，在汇编语言中，不但要使用转移指令，而且还要灵活运用，因为指令系统中有大量的转移指令。

转移指令分无条件转移指令和有条件转移指令两大类。

1、无条件转移指令(Transfer Unconditionally)

无条件转移指令包括：JMP、子程序的调用和返回指令、中断的调用和返回指令等。

下面只介绍无条件转移指令JMP(Unconditional Jump)。

JMP指令的一般形式：

JMP 标号/Reg/Mem

JMP指令是从程序当前执行的地方无条件转移到另一个地方执行。这种转移可以是一个短(short)转移(偏移量在[-128, 127]范围内)，近(near)转移(偏移量在[-32K, 32K]范围内)或远(far)转移(在不同的代码段之间转移)。

短和近转移是段内转移，JMP指令只把目标指令位置的偏移量赋值指令指针寄存器IP，从而实现转移功能。但远转移是段间转移，JMP指令不仅会改变指令指针寄存器IP的值，而且还会改变代码段寄存器CS的值。

该转移指令的执行不影响任何标志位。

例如：
 | …
next1: | …
 | JMP | next1 | ;向前转移，偏移量之差为负数
 | …
 | JMP | next2 | ;向后转移，偏移量之差为正数
 | …
next2: | …

在目前流行的汇编系统中，当段内转移时，有些软件把该转移指令默认为近转移，从而使指令的偏移量用一个字来表示，于是生成3个字节的指令代码，但如果程序员自己清楚转移的幅度在一个短转移的范围之内，那么，可用前置short的办法来告诉汇编程序，让它产生2个字节的指令代码。

比如：如果程序员知道在上例中的标号next2离“JMP next2”指令的偏移量不会超过127，那么，可用下面的转移方式来省掉一个字节的指令代码。

next2: | …
 | JMP | short next2 | ;生成2个字节的转移指令，从而节省一个字节
 | …
 | …


5.2.9-条件转移指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 条件转移指令

2、条件转移指令(Transfer Conditionally)

条件转移指令是一组极其重要的转移指令，它根据标志寄存器中的一个(或多个)标志位来决定是否需要转移，这就为实现多功能程序提供了必要的手段。微机的指令系统提供了丰富的条件转移指令来满足各种不同的转移需要，在编程序时，要对它们灵活运用。

条件转移指令又分三大类：基于无符号数的条件转移指令、基于有符号数的条件转移指令和基于特殊算术标志位的条件转移指令。

、无符号数的条件转移指令(Jumps Based on Unsigned (Logic) Data)

指令的助忆符 | 检测的转移条件 | 功能描述
JE/JZ | ZF=1 | Jump Equal or Jump Zero
JNE/JNZ | ZF=0 | Jump Not Equal or Jump Not Zero
JA/JNBE | CF=0 and ZF=0 | Jump Above or Jump Not Below or Equal
JAE/JNB | CF=0 | Jump Above or Equal or Jump Not Below
JB/JNAE | CF=1 | Jump Below or Jump Not Above or Equal
JBE/JNA | CF=1 or AF=1 | Jump Below or Equal or Jump Not Above

、有符号数的条件转移指令(Jumps Based on Signed (Arithmetic) Data)

指令的助忆符 | 检测的转移条件 | 功能描述
JE/JZ | ZF=1 | Jump Equal or Jump Zero
JNE/JNZ | ZF=0 | Jump Not Equal or Jump Not Zero
JG/JNLE | ZF=0 and SF=OF | Jump Greater or Jump Not Less or Equal
JGE/JNL | SF=OF | Jump Greater or Equal or Jump Not Less
JL/JNGE | SF≠OF | Jump Less or Jump Not Greater or Equal
JLE/JNG | ZF=1 or SF≠OF | Jump Less or Equal or Jump Not Greater

、特殊算术标志位的条件转移指令(Jumps Based on Special Arithmetic Tests)

指令的助忆符 | 检测的转移条件 | 功能描述
JC | CF=1 | Jump Carry
JNC | CF=0 | Jump Not Carry
JO | OF=1 | Jump Overflow
JNO | OF=0 | Jump Not Overflow
JP/JPE | PF=1 | Jump Parity or Jump Parity Even
JNP/JPO | PF=0 | Jump Not Parity or Jump Parity Odd
JS | SF=1 | Jump Sign (negative)
JNS | SF=0 | Jump No Sign (positive)

例5.15 编写一程序段，它把寄存器AX-BX的绝对值存入BX中。

解：
next: | …
 | SUB | BX, AX
 | JNS | next
 | NEG | BX
 | …

例5.16 已知一个字节变量char，试编写一程序段，把其所存的大写字母变成小写字母。

解：
next: | …
 | char | DB 'F' | ;变量说明
 | …
 | MOV | AL, char
 | CMP | AL, 'A'
 | JB | next | ;注意：字符是无符号数，不要使用指令JL
 | CMP | AL, 'Z'
 | JA | next
 | ADD | char, 20H | ;小写字母比大写字母的ASCII码大20H
 | …

如果不知道(或忘了)大小写字母ASCII码之间的关系，那么，可用数值表达式'a'-'A'、'b'-'B'、…、'z'-'Z'等来代替具体的数值20H。

例5.17 编写一段程序，完成下面计算公式，其中：变量X和Y都是字类型。

解：
 | …
 | X | DW ? | ;变量说明
 | Y | DW ?
 | … |
 | MOV | AX, X
 | MOV | BX, AX | ;用BX来临时存放计算结果
 | CMP | AX, 0
 | JLE | setdata
 | CMP | AX, 500
 | JG | case3
 | ADD | BX, 100D | ;BX=X+100
 | JMP | setdata
next: | SUB | BX, 50D | ;BX=X-50
setdata: | MOV | Y, BX | ;把计算结果赋给变量Y
 | …

例5.18 下面循环体的指令代码字节数超过128，试改写该循环。

 | …
 | MOV CX, COUNT | ;给循环计数器赋初值(＞0)
again: | 循环体指令序列 | ;循环体的首地址偏移量大于128
 | LOOP again
解：
 | …
 | MOV CX, COUNT
again: | 循环体指令序列
 | DEC CX
 | JNZ again | ;把LOOP指令改为条件转移指令


5.2.10 条件设置字节指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 条件设置字节指令

5.2.10 条件设置字节指令

条件设置字节指令(Set Byte Conditionally)是80386及其以后CPU所具有的一组指令。它们在测试条件方面与条件转移是一致的，但在功能方面，它们不是转移，而是根据测试条件的值来设置其字节操作数的内容为1或0。

条件设置字节指令的一般格式如下：

SETnn　Reg/Mem　　　　;80386+

其中：nn是表示测试条件的(见表5.4)，操作数只能是8位寄存器或一个字节单元。

这组指令的执行不影响任何标志位。

表5.4 条件设置字节指令列表

指令的助忆符 | 操作数和检测条件之间的关系
SETZ/SETE | Reg/Mem = ZF
SETNZSETNE | Reg/Mem = not ZF
SETS | Reg/Mem = SF
SETNS | Reg/Mem = not SF
SETO | Reg/Mem = OF
SETNO | Reg/Mem = not OF
SETP/SETPE | Reg/Mem = PF
SETNP/SETPO | Reg/Mem = not PF
SETC/SETB/SETNAE | Reg/Mem = CF
SETNC/SETB/SETAE | Reg/Mem = not CF
SETNA/SETBE | Reg/Mem = (CF or ZF)
SETA/SETNBE | Reg/Mem = not (CF or ZF)
SETL/SETNGE | Reg/Mem = (SF xor OF)
SETNL/SETGE | Reg/Mem = not (SF xor OF)
SETLE/SETNG | Reg/Mem = (SF xor OF) or ZF
SETNLE/SETG | Reg/Mem = not ((SF xor OF) or ZF)

例5.19 编写程序段：检测寄存器EAX的8个16进制中有几个0H，并把统计结果存入BH中。

解：
 | 方法1：用条件转移指令来实现
 | XOR | BH, BH
 | MOV | CX, 8 | ;测试寄存器EAX——8次
again: | TEST | AL, 0FH | ;测试低四位二进制是否为0H
 | JNZ | next
 | INC | BH
next: | ROR | EAX, 4 | ;循环向右移四位，为测试高四位作准备
 | LOOP | again
 | 方法2：用条件设置字节指令来实现
 | XOR | BH, BH
 | MOV | CX, 8 | ;测试寄存器EAX——8次
again: | TEST | AL, 0FH | ;测试低四位二进制是否为0H
 | SETZ | BL | ;如果AL的低四位是0，则BL置为1，否则，BL为0
 | ADD | BH, BL
 | ROR | EAX, 4
 | LOOP | again


5.2.11-字符串操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 字符串操作指令

5.2.11 字符串操作指令

字符串操作指令的实质是对一片连续存储单元进行处理，这片存储单元是由隐含指针DS:SI或ES:DI来指定的。字符串操作指令可对内存单元按字节、字或双字进行处理，并能根据操作对象的字节数使变址寄存器SI(和DI)增减1、2或4。具体规定如下：

(1)、当DF=0时，变址寄存器SI(和DI)增加1、2或4；
(2)、当DF=1时，变址寄存器SI(和DI)减少1、2或4。

在后面各指令中，有关变址寄存器都按上述规定进行增减，不再一一说明。

1、取字符串数据指令(Load String Instruction)

从由指针DS:SI所指向的内存单元开始，取一个字节、字或双字进入AL、AX或EAX中，并根据标志位DF对寄存器SI作相应增减。该指令的执行不影响任何标志位。

指令的格式：LODS 地址表达式
LODSB/LODSW
LODSD　　　　　　　　　　;80386+

在指令LODS中，它会根据其地址表达式的属性来决定读取一个字节、字或双字。即：当该地址表达式的属性为字节、字或双字时，将从指针DS:SI处读一个字节到AL中，或读一个字到AX，或读一个双字到EAX中，与此同时，SI还将分别增减1，2或4。

图5.15 取字符串数据指令的功能示意图

其它字符串指令中的“地址表达式”作用与此类似，将不再说明。

2、置字符串数据指令(Store String Instruction)

该指令是把寄存器AL、AX或EAX中的值存于以指针ES:DI所指向内存单元为起始的一片存储单元里，并根据标志位DF对寄存器DI作相应增减。该指令不影响任何标志位。

指令的格式：STOS 地址表达式
STOSB/STOSW
STOSD　　　　　　;80386+

图5.16 置字符串数据指令的功能示意图

3、字符串传送指令(Move String Instruction)

该指令是把指针DS:SI所指向的字节、字或双字传送给指针ES:DI所指向内存单元，并根据标志位DF对寄存器DI和SI作相应增减。该指令的执行不影响任何标志位。

指令的格式：MOVS 地址表达式1, 地址表达式2
MOVSB/MOVSW
MOVSD　　　　　　;80386+

图5.17 字符串传送指令的功能示意图


5.2.11-字符串比较指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 字符串比较指令

4、输入字符串指令(Input String Instruction)

该指令是从某一指定的端口接受一个字符串，并存入一片存储单元之中。输入端口由DX指定，存储单元的首地址和读入数据的个数分别由ES:DI和CX来确定。在指令的执行过程中，还根据标志位DF对寄存器DI作相应增减。该指令不影响任何标志位。

与指令有关的操作数ES、DI、DX和CX等都是隐含操作数。

指令的格式：INS 地址表达式
INSB/INSW
INSD　　　　　　　;80286+

5、输出字符串指令(Output String Instruction)

该指令是把一个字符串输入到指定的输出端口中。输出端口由DX指定，其输出数据的首地址和个数分别由DS:SI和CX来确定。在指令的执行过程中，还根据标志位DF对寄存器SI作相应增减。该指令的执行不影响任何标志位。

与指令有关的操作数DS、SI、DX和CX等都是隐含操作数。

指令的格式：OUTS 地址表达式
OUTSB/OUTSW
OUTSD　　　　　　;80286+

6、字符串比较指令(Compare String Instruction)

该指令是把指针DS:SI和ES:DI所指向字节、字或双字的值相减，并用所得到的差来设置有关的标志位。与此同时，变址寄存器SI和DI也将根据标志位DF的值作相应增减。

指令的格式：CMPS　地址表达式1, 地址表达式2
CMPSB/CMPSW
CMPSD　　　　　　;80386+

受影响的标志位：AF、CF、OF、PF、SF和ZF

图5.18 字符串比较指令的功能示意图

7、字符串扫描指令(Scan String Instruction)

该指令是用指针ES:DI所指向字节、字或双字的值与相应的AL、AX或EAX的值相减，用所得到的差来设置有关标志位。与此同时，变址寄存器DI还将根据标志位DF的值进行增减。

指令的格式：SCAS　地址表达式1
SCASB/SCASW
SCASD　　　　　　;80386+

受影响的标志位：AF、CF、OF、PF、SF和ZF

图5.19 字符串扫描指令的功能示意图


5.2.11-重复字符串操作指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 重复字符串操作指令

8、重复字符串操作指令(Repeat String Instruction)

前面介绍了七种不同的字符串操作指令：取字符串数据、置字符串数据、字符串传送、输入字符串、输出字符串、字符串比较和字符串扫描等指令，所叙述是这些指令执行一次所具有的功能。但我们知道：每个字符串通常会有多个字符的，所以，就需要重复执行这些字符串操作指令。为了满足这种需求，指令系统提供了一组重复前缀指令。

虽然在这些字符串指令的前面都可以添加一个重复前缀指令，但由于指令执行结果的差异，对某个具体的字符串指令又不用重复前缀指令而改用其它循环来实现重复的需要。

重复字符串操作指令对标志位的影响是由被重复的字符串操作指令来决定。

重复前缀指令REP(Repeat String Instruction)

重复前缀指令是重复其后的字符串操作指令，重复的次数由CX来决定。其一般格式为：

REP　LODS/LODSB/LODSW/LODSD
REP　STOS/STOSB/STOSW/STOSD
REP　MOVS/MOVSB/MOVSW/MOVSD
REP　INS/ INSB/INSW/INSD
REP　OUTS/OUTSB/OUTSW/OUTSD

重复前缀指令的执行步骤如下：

(1)、判断：CX=0；
(2)、如果CX=0，则结束重复操作，执行程序中的下一条指令；
(3)、否则，CX=CX-1(不影响有关标志位)，并执行其后的字符串操作指令，在该指令执行完后，再转到步骤(1)。

从上面的重复前缀指令格式来看，虽然我们可以使用重复取字符串数据指令(第一组指令)，但可能会因为指令的执行结果而在程序中几乎不被使用。

例5.20 编写一段程序，计算字符串“12345abcdefgh”中字符的ASCII之和。

解：
 | …
 | MESS | DB '12345abcdefgh' | ;在数据段中进行变量说明
 | …
 | MOV | AX, SEG MESS
 | MOV | DS, AX
 | LEA | SI, MESS | ;用DS:SI来指向字符串的首地址
 | MOV | CX, 13D | ;重复次数
 | XOR | BX, BX | ;置求和的初值为0
 | REP | LODSB
 | … |

虽然指令“REP LODSB”能从字符串中取出每个字符，但它是在一条指令中完成的，程序的其它指令根本无法处理每次取出的数据，指令的执行结果是：AL只保存最后一次所取出的字符'h'的ASCII码。

所以，为了实现本例的要求，不能使用重复前缀指令，而要把指令“REP LODSB”改写成如下四条指令：

 | XOR | AH, AH | ;为后面的累加作准备
again: | LODSB
 | ADD | BX, AX | ;AL是被取出的字符，AH已被清0
 | LOOP | again

条件重复前缀指令(Repeat String Conditionally)

条件重复前缀指令与前面的重复前缀指令功能相类似，所不同的是：其重复次数不仅由CX来决定，而且还会由标志位ZF来决定。根据ZF所起的作用又分为二种：相等重复前缀指令REPE/REPZ和不等重复前缀指令REPE/REPZ。

A、相等重复前缀指令的一般格式为：

REPE/REPZ　SCAS/SCASB/SCASW/SCASD
REPE/REPZ　CMPS/CMPSB/CMPSW/CMPSD

该重复前缀指令的执行步骤如下：

(1)、判断条件：CX≠0 且 ZF=1；
(2)、如果条件不成立，则结束重复操作，执行程序中的下一条指令；
(3)、否则，CX=CX-1(不影响有关标志位)，并执行其后的字符串操作指令，在该指令执行完后，再转到步骤(1)。

B、不等重复前缀指令的一般格式为：

REPNE/REPNZ　SCAS/SCASB/SCASW/SCASD
REPNE/REPNZ　CMPS/CMPSB/CMPSW/CMPSD

该重复前缀指令的执行步骤如下：

(1)、判断条件：CX≠0 且 ZF=0；
(2)、如果条件不成立，则结束重复操作，执行程序中的下一条指令；
(3)、否则，CX=CX-1(不影响有关标志位)，并执行其后的字符串操作指令，在该指令执行完后，再转到步骤(1)。


5.2.12-ASCII-BCD调整指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → ASCII-BCD码运算调整指令

5.2.12 ASCII--BCD码运算调整指令

前面介绍的算术运算指令都是针对二进制数进行操作的指令，但对绝大多数人来说，十进制是最简单、熟悉的。为了方便按十进制数进行算术运算，指令系统专门提供了一组十进制运算调整指令。

虽然人们会觉得按十进制进行算术运算很自然，但计算机要化更多的时间来完成相应操作。在通常情况下，这组指令很少被程序员运用在实际的程序之中。所以，这组指令的使用率较低，可以根据需要有选择性地学习。

1、ASCII码加调整指令AAA(Ascii Adjust After Addition)

该指令是用于调整AL之值，该值是二个ASCII码字节相加之和。具体的调整规则如下：

、若AL的低四位大于9，或标志位AF=1，则，AH=AH+1，AL=AL+6，并置AF和CF为1，否则，只置AF和CF为0；
、清除AL的高四位。

图5.20 AAA指令执行过程示意图

指令的格式：AAA
受影响的标志位：AF和CF(OF、PF、SF和ZF等都是无定义)

例5.21 编写一段程序，完成二个15位十进制数X和Y之和，并把计算结果存入X之中。假设数据X和Y都是以字符串形式表示的。

解：
 | …
 | X | db "456407983123186" | ;任意假设二个15位的大数
 | Y | db "326676709587211"
 | …
 | CLC
 | MOV | SI, 14 | ;用变址寄存器SI来从字符串的后面向前访问
 | MOV | CX, 15 | ;因为它们是二个15位十进制数
loop1: | MOV | AL, X[SI]
 | ADC | AL, Y[SI] | ;把被加数加上
 | AAA
 | MOV | X[SI], AL
 | DEC | SI
 | LOOP | loop1 | ;15位十进制数相加完毕
 | LEA | BX, X | ;下面5条指令是把X中的数据变成对应的字符
 | MOV | CX, 15
loop2: | ADD | byte ptr [BX], '0'
 | INC | BX
 | LOOP | loop2
 | …

从上例可以看出，其实任意位的十进制数也都是可以的，只要改变CX的值即可。

2、ASCII码减调整指令AAS(Ascii Adjust After Subtraction)

该指令是用于调整AL之值，该值是二个ASCII码字节相减之差。具体的调整规则如下：

、若AL的低四位大于9，或标志位CF=1，则，AH=AH-1，AL=AL-6，并置AF和CF为1，否则，只置AF和CF为0；
、清除AL的高四位。

指令的格式：AAS
受影响的标志位：AF和CF(OF、PF、SF和ZF等都是无定义)

图5.21 AAS指令执行过程示意图


5.2.12-乘、除法调整指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → ASCII码乘调整指令

3、ASCII码乘调整指令AAM(Ascii Adjust After Multiplication)

该指令是用于调整寄存器AL之值，该值是由二个单BCD码字节用无符号乘指令MUL所得的积。其调整规则如下：

AH←AL/10(商)，AL←AL%10(余数)

指令的格式：AAM
受影响的标志位：PF、SF和ZF(AF、CF和OF等都是无定义)

例如：

MOV　AL, 9
MOV　BL, 8
MUL　BL　　　　　　　;AL=72D
AAM　　　　　　　　　;AH=7, AL=2

4、ASCII码除调整指令AAD(Ascii Adjust After Division)

该指令是在作除法前用于调整寄存器AH和AL之值，它是把二个寄存器中单BCD码组成一个十进制数值，为下面的除法作准备的。其调整规则如下：

AL←AH*10+AL，AH←0

指令的格式：AAD
受影响的标志位：PF、SF和ZF(AF、CF和OF等都是无定义)

例如：

MOV　AX, 0502H
MOV　BL, 10D
AAD　　　　　　　　　;AH=0, AL=52D
DIV　 BL　　　　　　　;AH=2(余数), AL=5(商)


5.2.12-十进制调整指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 十进制数加调整指令

5、十进制数加调整指令DAA(Decimal Adjust After Addition)

该指令是用于调整AL的值，该值是由指令ADD或ADC运算二个压缩型BCD码所得到的结果。压缩型BCD码是一个字节存放二个BCD码，低四位和高四位都是一个BCD码。

其调整规则如下：

、如果AL的低四位大于9，或标志位AF=1，那么，AL=AL+6，并置AF=1；
、如果AL的高四位大于9，或CF=1，那么，AL=AL+60H，并置CF=1；
、如果以上两点都不成立，则，清除标志位AF和CF。

经过调整后，AL的值仍是压缩型BCD码，即：二个压缩型BCD码相加，并进行调整后，得到的结果还是压缩型BCD码。

指令的格式：DAA
受影响的标志位：AF、CF、PF、SF和ZF(OF无定义)

例如：

MOV　AL, 43H
MOV　BL, 29H
ADD　AL, BL　　　　　;AL=6BH，其不是压缩型的BCD码，因为低四位'B'不是BCD码
DAA　　　　　　　　　;调整后，AL=72H，这是压缩型的BCD码，也有：43+29=72

6、十进制数减调整指令DAS(Decimal Adjust After Subtraction)

该指令也是用于调整AL的值，该值是由指令SUB或SBB运算二个压缩型BCD码所得到的结果。其调整规则如下：

、如果AL的低四位大于9，或标志位AF=1，那么，AL=AL-6，并置AF=1；
、如果AL的高四位大于9，或CF=1，那么，AL=AL-60H，并置CF=1；
、如果以上两点都不成立，则，清除标志位AF和CF。

经过调整后，AL的值仍是压缩型BCD码，即：二个压缩型BCD码相减，并进行调整后，得到的结果还是压缩型BCD码。

指令的格式：DAS
受影响的标志位：AF、CF、PF、SF和ZF(OF无定义)

例如：

MOV　AL, 43H
MOV　BL, 29H
SUB　AL, BL　　　　;AL=1AH，其不是压缩型的BCD码，因为低四位'A'不是BCD码
DAS　　　　　　　　;调整后，AL=14H，这是压缩型的BCD码，也有：43-29=14


5.2.13 处理器指令

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 指令系统 → 处理器指令

5.2.13 处理器指令

处理器指令是一组控制CPU工作方式的指令。这组指令的使用频率不高。

1、空操作指令NOP(No Operation Instruction)

该指令没有的显式操作数，主要起延迟下一条指令的执行。通常用执行指令“XCHG AX, AX”来代表它的执行。NOP指令的执行不影响任何标志位。

指令的格式：NOP

2、等待指令WAIT(Put Processor in Wait State Instruction)

该指令使CPU处于等待状态，直到协处理器(Coprocessor)完成运算，并用一个重启信号唤醒CPU为止。该指令的执行不影响任何标志位。

指令的格式：WAIT

3、暂停指令HLT(Enter Halt State Instruction)

在等待中断信号时，该指令使CPU处于暂停工作状态，CS:IP指向下一条待执行的指令。当产生了中断信号，CPU把CS和IP压栈，并转入中断处理程序。在中断处理程序执行完后，中断返回指令IRET弹出IP和CS，并唤醒CPU执行下条指令。

指令的格式：HLT

指令的执行不影响任何标志位。

4、封锁数据指令LOCK(Lock Bus Instruction)

该指令是一个前缀指令形式，在其后面跟一个具体的操作指令。LOCK指令可以保证是在其后指令执行过程中，禁止协处理器修改数据总线上的数据，起到独占总线的作用。该指令的执行不影响任何标志位。

指令的格式：LOCK INSTRUCTION


5.3 习题

1 指令格式
1 指令格式
2 了解指令的几个方面
2 了解指令的几个方面
1 MOV
1 MOV
2 MOVSX/MOVZX
2 MOVSX/MOVZX
3 XCHG
3 XCHG
4 LEA
4 LEA
5 LDS/LES/LFS/LGS/LSS
5 LDS/LES/LFS/LGS/LSS
6 PUSH/POP
6 PUSH/POP
7 XLAT
7 XLAT
1 进位CF操作指令
1 进位CF操作指令
2 方向位DF操作指令
2 方向位DF操作指令
3 中断允许位IF操作指令
3 中断允许位IF操作指令
4 存取标志位操作指令
4 存取标志位操作指令
5 标志位堆栈操作指令
5 标志位堆栈操作指令
1 ADD/ADC/INC/XADD
1 ADD/ADC/INC/XADD
2 SUB/SBB/DEC/NEG
2 SUB/SBB/DEC/NEG
3 MUL/IMUL
3 MUL/IMUL
4 DIV/IDIV
4 DIV/IDIV
5 CBW/CWD/CDWE/CDQ
5 CBW/CWD/CDWE/CDQ
1 算术移位:SAL/SAR
1 算术移位:SAL/SAR
2 逻辑移位:SHL/SHR
2 逻辑移位:SHL/SHR
3 双精度移位:SHLD/SHRD
3 双精度移位:SHLD/SHRD
4 循环移位:ROL/ROR
4 循环移位:ROL/ROR
5 带进位循环移位:RCL/RCR
5 带进位循环移位:RCL/RCR
1 BSF/BSR
1 BSF/BSR
2 BT/BTC/BTR/BTS
2 BT/BTC/BTR/BTS
3 TEST
3 TEST
1 LOOP
1 LOOP
2 LOOPE/LOOPZ
2 LOOPE/LOOPZ
3 LOOPNE/LOOPNZ
3 LOOPNE/LOOPNZ
4 JCXZ/JECXZ
4 JCXZ/JECXZ
1 无条件转移指令(JMP)
1 无条件转移指令(JMP)
2 条件转移指令
2 条件转移指令
1 LODS/STOS/MOVS
1 LODS/STOS/MOVS
2 INS/OUTS
2 INS/OUTS
3 CMPS/SCAS
3 CMPS/SCAS
4 REP/REPE/REPNE
4 REP/REPE/REPNE
1 AAA/AAS
1 AAA/AAS
2 AAM/AAD
2 AAM/AAD
3 DAA/DAS
3 DAA/DAS
1 数据传送指令
1 数据传送指令
2 标志位操作指令
2 标志位操作指令
3 算术运算指令
3 算术运算指令
4 逻辑运算指令
4 逻辑运算指令
5 移位操作指令
5 移位操作指令
6 位操作指令
6 位操作指令
7 比较运算指令
7 比较运算指令
8 循环指令
8 循环指令
9 转移指令
9 转移指令
10 条件设置字节指令
10 条件设置字节指令
11 字符串操作指令
11 字符串操作指令
12 ASCII--BCD码调整指令
12 ASCII--BCD码调整指令
13 处理器指令
13 处理器指令
本章的学习内容和要求
本章的学习内容和要求
5.1 汇编语言指令格式
5.1 汇编语言指令格式
5.2 指令系统
5.2 指令系统
5.3 习题
5.3 习题

→ 习题

5.3 习题

5.1、书写正确的MOV指令，需要符合哪些规定？在32位CPU中，增加了什么指令来克服不同数据类型之间的数据传送？
5.2、简述指令MOV BX, 1234H、MOV BX, [1234]和MOV BX, offset [1234H]的功能和它们之间区别。
5.3、简述指令MOV BX, Data和MOV BX, offset Data之间的区别。
5.4、选择指令实现下列要求，OPN1 ← OPN2的含义：把操作数OPN2的值传送给OPN1。
1)、EBX ← EDX 3)、SI ← BX 5)、AL ← BH 7)、AX ← 13AH 9)、SI ← 1000H |  |  | 2)、BX ← CL 4)、DS ← AX 6)、AL ← 12H 8)、CX ← 0CDH (假设0CDH为无符号数) 10)、EBX ← 1200A2H
5.5、PUSHA指令能把哪些寄存器压栈？它们进栈的顺序是什么？可用什么指令把这些寄存器依次弹出堆栈？
5.7、PUSHAD指令完成什么操作？它与什么操作的作用相反？
5.8、在16位/32位CPU中，标志位寄存器的内容进栈和出栈的指令是什么?
5.9、写出下列指令序列中每条指令的执行结果，请在Debug环境下验证之，并注意各标志位的变化情况。
MOV BX, 23ABH ADD BL, 0ACH MOV AX, 23F5H ADD BH, AL SBB BX, AX ADC AX, 12H SUB BH, -9
5.10、假定DS=1123H，SS=1400H，BX=0200H，BP=1050H，DI=0400H，SI=0500H，LIST的偏移量为250H，试确定下面各指令访问内存单元的地址。
 | 1)、MOV | AL, [1234H] | 2)、MOV | AX, [BX]
 | 3)、MOV | [DI], AL | 4)、MOV | [2000H], AL
 | 5)、MOV | AL, [BP+DI] | 6)、MOV | CX, [DI]
 | 7)、MOV | EDX, [BP] | 8)、MOV | LIST[SI], EDX
 | 9)、MOV | CL, LIST[BX+SI] | 10)、MOV | CH, [BX+SI]
 | 11)、MOV | EAX, [BP+200H] | 12)、MOV | AL, [BP+SI+200H]
 | 13)、MOV | AL, [SI-0100H] | 14)、MOV | BX, [BX+4]
5.11、用一条指令实现把BX和SI之和传送给CX。
5.12、试比较下列三组指令的功能。
 | 1)、LDS SI, [DI] |  | 2)、MOV SI, [DI] 3)、MOV SI, [DI+2] MOV SI, [DI+2] MOV SI, [DI]
5.13、标志位CF有着重要的作用，请简述之，并至少写出三种把CF置0的方法。
5.14、写一个计算DL三次方的指令序列，假设幂不超过16位二进制。
5.15、写出一个指令序列实现：用CL中数据除BL中的数据，然后商乘2，最后的结果存入DX中。
5.16、按下列要求编写指令序列。
1)、清除DH中的最低三位而不改变其它位，结果存入BH中； 2)、把DI中的最高5位置1而不改变其它位； 3)、把AX中的0~3位置1，7~9位取反，13~15位置0； 4)、检查BX中的第2、5和9位中是否有一位为1； 5)、检查CX中的第1、6和11位中是否同时为1； 6)、检查AX中的第0、2、9和13位中是否有一位为0； 7)、检查DX中的第1、4、11和14位中是否同时为0；
5.17、简述指令AND和TEST、NOT和NEG之间的区别。
5.18、选择适当的指令实现下列功能。
1)、右移DI三位，并把零移入最高位； 2)、把AL左移一位，使0移入最低一位； 3)、AL循环左移三位； 4)、EDX带进位位循环右移四位； 5)、DX右移六位，且移位前后的正负性质不变。
5.19、判断下列指令的正确性，若某指令是错误的，请说明其错误原因。
 | 1)、MOV | BL, CX | 2)、MOV | DS, SS
 | 3)、MOV | [BX], [DI] | 4)、MOV | AL, [BX][SI]
 | 5)、MOV | ES, AL | 6)、MOV | DS, DX
 | 7)、MOV | CS, AX | 8)、MOV | BX, CS
 | 9)、MOV | DS, 1230H | 10)、MOVSX | DS, AL
 | 11)、XCHG | BX, 3 | 12)、POP | CS
 | 13)、MOV | IP, SI | 14)、PUSH | CS
 | 15)、PUSH | BL | 16)、MOV | [SP], BX
 | 17)、MOV | AX, BX+3 | 18)、MOV | AX, [BX+3]
 | 19)、MOV | BX, [BX] | 20)、MOV | BH, [BL]
 | 21)、XCHG | ES, AX | 22)、LEA | AX, [BX+SI]
 | 23)、MUL | 10H | 24)、IMUL | DX, 10H
 | 25)、DIV | 10 | 26)、IDIV | DX, 10H
 | 27)、SHL | AX, CX | 28)、SHR | BX, CH
 | 29)、ROL | BX, 20 | 30)、RCR | AX, CL
 | 31)、CMP | AX,1234H | 32)、CMP | 12H, CL
 | 33)、JCXZ | next | 34)、JEBXZ | next (假设标号next已存在)
5.20、方向标志DF的作用是什么？用于设置或消除该标志位的指令是什么？
5.21、串指令用DI和SI寻址哪些内存段中的内存数据？
5.22、段间转移和段内转移之间的区别是什么？
5.23、SCASB指令的作用是什么？叙述指令REPE SCASB指令所完成的功能。
5.24、指令REPNE SCASB结束执行的条件是什么？
5.25、REP前缀的作用是什么？能用指令REP LODSB读取DS:SI所指内存中的每个字符来进行处理吗？若不能，请说明原因。
5.26、编写指令序列，在字符串LIST中查寻字符'B'，若找到，则转向Found，否则，转向NotFound，假设该字符串含有300个字符。
5.27、编写指令序列，把Source存储区域中的12个字节传送到Dest存储区域内。
5.28、设计一个短指令序列，将32位数AX:BX中的8位BCD与CX:DX中的8位BCD相加，并把所得结果存入CX:DX中。
5.29、有符号数比较后，用什么样的条件转移指令实现转移？无符号数比较后，用什么样的条件转移指令实现转移？


第6章 程序的基本结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

第6章 程序的基本结构

在前面几章，我们分别介绍了用汇编语言进行程序设计所需要的几个最基本的知识：内存单元的寻址方式，变量定义和各种汇编指令格式。在掌握了这些基本内容之后，就需要学习如何把它们组成一个完整的汇编语言程序。

6.1 源程序的基本组成

汇编语言源程序的组成部分有：模块、段、子程序和宏等。一个模块对应一个目标文件，当开发较大型的应用程序时，该程序可能由若干个目标文件或库结合而成的。有关模块和子程序的知识和宏在第7章介绍，有关宏的知识将在第9章中叙述。

6.1.1 段的定义

微机系统的内存是分段管理的，为了与之相对应，汇编语言源程序也分若干个段来构成。8086CPU有四个段寄存器，在该系统环境下运行的程序在某个时刻最多可访问四个段，而80386及其以后的CPU都含有六个段寄存器，于是，在这些系统环境下开发的运行程序在某个时刻最多可访问六个段。

不论程序在某个时刻最多能访问多少个段，在编程序时，程序员都可以定义比该段数更多的段。在通常情况下，一个段的长度不能超过64K，在80386及其以后系统的保护方式下，段基地址是32位，段的最大长度可达4G。

段的长度是指该段所占的字节数：

、如果段是数据段，则其长度是其所有变量所占字节数的总和；
、如果段是代码段，则其长度是其所有指令所占字节数的总和。

在定义段时，每个段都有一个段名。在取段名时，要取一个具有一定含义的段名。

段定义的一般格式如下：
 | 段名 | SEGMENT | [对齐类型] [组合类型] [类别]
 |  | … | ;段内的具体内容
 |  | …
 | 段名 | ENDS

其中：“段名”必须是一个合法的标识符，前后二个段名要相同。可选项“对齐类型”、“组合类型”和“类别”的说明作用请见6.3节中的叙述。

一个数据段的定义例子：
 | DATA1 | SEGMENT
 |  | word1 | DW | 1, 9078H, ?
 |  | byte1 | DB | 21, 'World'
 |  |  | DD | 12345678H
 | DATA1 | ENDS
一个代码段的例子：
 | CODE1 | SEGMENT
 |  | MOV | AX, DATA1 | ;把数据段DATA1的段值送AX
 |  | MOV | DS, AX | ;把AX的值送给DS，即：DS存储数据段的段值
 |  | …
 |  | MOV | AX, 4C00H
 |  | INT | 21H | ;调用DOS功能，结束程序的运行
 | CODE1 | ENDS


6.1.2 段寄存器的说明语句

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 源程序的基本组成 → 段寄存器的说明语句

6.1.2 段寄存器的说明语句

在汇编语言源程序中可以定义多个段，每个段都要与一个段寄存器建立一种对应关系。建立这种对应关系的说明语句格式如下：

ASSUME 段寄存器名:段名[, 段寄存器名:段名, ……]

其中：段寄存器是CS、DS、ES、SS、FS和GS，段名是在段定义语句说明时的段名。

在一条ASSUME语句中可建立多组段寄存器与段之间的关系，每种对应关系要用逗号分隔。例如，

ASSUME CS:CODE1, DS:DATA1

上面的语句说明了：CS对应于代码段CODE1，DS对应于数据段DATA1。

在ASSUME语句中，还可以用关键字NOTHING来说明某个段寄存器不与任何段相对应。下面语句说明了段寄存器ES不与某段相对应。

ASSUME ES:NOTHING

在通常情况下，代码段的第一条语句就是用ASSUME语句来说明段寄存器与段之间的对应关系。在代码段的其它位置，还可以用另一个ASSUME语句来改变前面ASSUME语句所说明的对应关系，这样，代码段中的指令就用最近的ASSUME语句所建立的对应关系来确定指令中的有关信息。

例6.1 汇编语言段及其段说明语句的作用。
 | DATA1 | SEGMENT |  | ;定义数据段DATA1
 |  | word1 | DW 5678h
 |  | byte1 | DB "ABCDEFG"
 | DATA1 | ENDS
 | DATA2 | SEGMENT |  | ;定义数据段DATA2
 |  | word2 | DW 1234h
 |  | word3 | DW 9876h
 | DATA2 | ENDS
 | DATA3 | SEGMENT |  | ;定义数据段DATA3
 |  | byte2 | DB ?
 | DATA3 | ENDS
 | CODE1 | SEGMENT |  | ;编写代码段CODE1
 |  | ASSUME | CS:CODE1, DS:DATA1, ES:DATA2 | ;(1)
 |  | MOV | AX, DATA1 | ;(2)
 |  | MOV | DS, AX | ;(3)
 |  | MOV | AX, DATA2 | ;(4)
 |  | MOV | ES, AX | ;(5)
 |  | …
 |  | MOV | AX, word1 | ;访问段DATA1中的字变量word1
 |  | MOV | word2, AX | ;访问段DATA2中的字变量word2
 |  | …
 |  | ASSUME | DS:DATA3, ES:NOTHING | ;(6)
 |  | MOV | AX, DATA3
 |  | MOV | DS, AX
 |  | MOV | BL, byte2 | ;访问段DATA3中的字节变量byte2
 |  | …
 |  | MOV | AX, 4C00H | ;(7)
 |  | INT | 21H | ;(8)
 | CODE1 | ENDS

语句(1)和(6)分别说明了段和段寄存器之间的对应关系，其中语句(6)重新说明语句(1)所指定的对应关系。

二组语句(2)和(3)、(4)和(5)实现对段寄存器DS和ES赋初值。ASSUME说明语句只起说明作用，它不会对段寄存器赋值，所以，必须对有关段寄存器赋值。在以后的其它源程序中也都是用此方法来实现对数据段寄存器赋值的。

语句(7)和(8)是调用中断21H的4CH号功能来结束本程序的执行，程序的返回代码由寄存器AL来确定。结束本程序执行的指令是所有主模块必须书写的语句。

注意：代码段寄存器不能由程序员在源程序中对其赋值，其值是由操作系统在装入它进入系统运行时自动赋值的。


6.1.3 堆栈段的说明

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 源程序的基本组成 → 堆栈段的说明

6.1.3 堆栈段的说明

堆栈段是一个特殊的段，在程序中可以定义它，也可以不定义。除了要生成COM型执行文件的源程序外，一个完整的源程序一般最好定义堆栈段。如果在程序中不定义堆栈段，那么，操作系统在装入该执行程序时将自动为其指定一个64K字节的堆栈段。

在程序没有定义堆栈段的情况下，在由连接程序生成执行文件时，将会产生一条如下的警告信息，但程序员可以不理会它，所生成的执行文件是可以正常运行的。

warning xxxx: no stack segment (其中：xxxx是错误号)

在源程序中，可用以下方法来定义堆栈段。

方法1：
 | STACK1 | SEGMENT
 |  |  | DB 256 DUP(?) | ;256是堆栈的长度，可根据需要进行改变
 |  | TOP | LABEL WORD
 | STACK1 | ENDS

以上堆栈段的定义如图6.1所示。由于堆栈是按地址从大到小的存储单元顺序来存放内容的，所以，在堆栈存储单元说明语句之后，再说明一个栈顶别名，这样，对栈顶寄存器SP的赋值就很方便。

在源程序的代码段中，还要添加如下程序段，才能把段STACK1当作堆栈段来使用。

图6.1 堆栈段定义示意图

…
ASSUME | SS:STACK1 | ;可在代码段的段指定语句中一起说明
CLI |  | ;禁止响应可屏蔽中断
MOV | AX, STACK1
MOV | SS, AX
MOV | SP, offset TOP | ;给堆栈段的栈顶寄存器SP赋初值
STI |  | ;恢复响应可屏蔽中断
…
方法2：
 | STACK1 | SEGMENT STACK | ;定义一个堆栈段，其段名为STACK1
 |  | DB 256 DUP(?)
 | STACK1 | ENDS

上述段定义说明了该段是堆栈段，系统会自动把段寄存器SS和栈顶寄存器SP与该堆栈段之间建立相应的关系，并设置其初值，而不用在代码段对它们进行赋值。

除了以上二种方法外，还有一种更简洁的方法来定义堆栈段，有关内容请见第6.4.2节中的叙述。


6.1.4 源程序的结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 源程序的基本组成 → 源程序的结构

6.1.4 源程序的结构

下面的程序是一个完整的源程序，其功能是在屏幕上显示字符串“Hello, World.”。读者可参考此结构编写自己的简单程序。

例6.2 在屏幕上显示字符串“HELLO，WORLD.”

解：可运行下面的控件，用鼠标左键单击程序中的某一行，可阅读其含义；单击“内存”可切换内存内容的显示方式。

伪指令END表示源程序到此为止，汇编程序对该语句之后的任何内容都不作处理，所以，通常情况下，伪指令END是源程序的最后一条语句。

伪指令END后面可附带一个在程序中已定义的标号，由该标号指明程序的启动位置。

如果源程序是一个独立的程序或主模块，那么，伪指令END后面一定要附带一个标号；如果源程序仅是一个普通模块，那么，其END后面就一定不能附带标号。


6.2 程序的基本结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构

6.2 程序的基本结构

在学习高级语言程序设计时，我们知道了程序的三大主要结构：顺序结构、分支结构和循环结构。在汇编语言的源程序也同样有此三大结构，所不同的是它们的表现形式不同。用高级语言编写程序时，由于不使用“转移语句”而使这三种结构清晰明了。

但在汇编语言的源程序中，很难不使用“转移语句”(除非是一些只有简单功能的程序)，有时甚至会有各种各样的“转移语句”。由于存在这些转移语句，就使得：汇编语言源程序的基本结构显得不太明确。如果源程序的编写者思维混乱，编写出来的源程序在结构上就会显得杂乱无章，反之，如果编写者条理清晰，安排的操作井然有序，那么，编写出来的程序在结构上就会一目了然。

总之，不论是高级语言的源程序，还是汇编语言的源程序，其程序的三大基本结构也还是万变不离其宗的。

6.2.1 顺序结构

顺序结构是最简单的程序结构，程序的执行顺序就是指令的编写顺序，所以，安排指令的先后次序就显得至关重要。另外，在编程序时，还要妥善保存已得到的处理结果，为后面的进一步处理直接提供前面的处理结果，从而避免不必要的重复操作。

例6.3 编写程序段，完成下面公式的计算(其中：变量X和Y是32位有符号数，变量A，B和Z是16位有符号数)。

A←(X-Y+24)/Z的商，B←(X-Y+24)/Z的余数

解：
 | DATA1 | SEGMENT
 |  | X | DD ?
 |  | Y | DD ?
 |  | Z | DW ?
 |  | A | DW ?
 |  | B | DW ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, X
 |  | MOV | DX, X+2 | ;用(DX:AX)来保存32位变量X的数值
 |  | SUB | AX,Y
 |  | SBB | DX, Y+2 | ;(DX:AX)-(Y+2:Y)
 |  | ADD | AX, 24D
 |  | ADC | DX, 0 | ;(DX:AX)+24
 |  | IDIV | Z
 |  | MOV | A, AX
 |  | MOV | B, DX
 |  | …
 | CODE1 | ENDS

在编程序时，常常需要交换二变量之值。假设需要交换值的变量名为：var1和var2，临时增加的变量名为temp。常用的算法如下：

temp = var1
var1 = var2
var2 = temp


6.2 程序的基本结构举例

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构

例6.4 假设有二个字变量word1和word2，编写程序段实现交换其值的功能。

解：
方法1：用汇编语言指令简单“直译”上面的 交换数据方法
 | DATA1 | SEGMENT
 |  | …
 |  | word1 | DW ?
 |  | word2 | DW ?
 |  | temp | DW ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, word1
 |  | MOV | temp, AX | ;上二语句实现语句“temp=word1”
 |  | MOV | AX, word2
 |  | MOV | word1, AX | ;上二语句实现语句“word1=word2”
 |  | MOV | AX, temp
 |  | MOV | word2, AX | ;上二语句实现语句“word2=temp”
 |  | …
 | CODE1 | ENDS

这种方法虽然也能完成功能，但显然其不能充分利用汇编语言的特点，程序效率很低。

方法2：用汇编语言指令的特点来直接编译
 | DATA1 | SEGMENT
 |  | …
 |  | word1 | DW ?
 |  | word2 | DW ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, word1
 |  | XCHG | AX, word2
 |  | MOV | word1, AX | ;能XCHG word1, word2来代替这三条指令吗？
 |  | …
 | CODE1 | ENDS

该方法充分利用了汇编语言的特点，不仅省去了中间变量temp的定义，而且程序的效率也提高了。


6.2.2 分支结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 分支结构

6.2.2 分支结构

分支结构是一种非常重要的程序结构，也是实现程序功能选择所必要的程序结构。由于汇编语言需要书写转移指令来实现分支结构，而转移指令肯定会破坏程序的结构，所以，编写清晰的分支结构是掌握该结构的重点，也是用汇编语言编程的基本功。

在程序中，当需要进行逻辑分支时，可用每次分二支的方法来达到程序最终分多支的要求，也可是用地址表的方法来达到分多支的目的。

一、显示转移指令实现的分支结构

在高级语句中，分支结构一般用IF语句来实现，在汇编语言中，课用无条件转移指令或条件转移指令实现的分支结构。如图6.2给出了二种常用的分支结构。

在编写分支程序时，要尽可能避免编写“头重脚轻”的结构，即：当前分支条件成立时，将执行一系列指令，而条件不成立时，所执行的指令很少。这样就使后一个分支离分支点较远，有时甚至会遗忘编写后一分支程序。这种分支方式不仅不利于程序的阅读，而且也不便将来的维护。

所以，在编写分支结构时，一般先处理简单的分支，再处理较复杂的分支。对多分支的情况，也可遵循“由易到难”的原则。因为简单的分支只需要较少的指令就能处理完，一旦处理完这种情况后，在后面的编程过程中就可集中考虑如何处理复杂的分支。
(a) if … endif结构 | (b) if…else…endif结构
图6.2 分支结构的二种结构

例6.5 已知字节变量CHAR1，编写一程序段，把它由小写字母变成大写字母。

解：
 | DATA1 | SEGMENT
 |  | …
 |  | CHAR1 | DB ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AL, CHAR1
 |  | CMP | AL, ‘a’
 |  | JB | next
 |  | CMP | AL, ‘z’
 |  | JA | next
 |  | SUB | CHAR1, 20H | ;指令AND CHAR1, 0DFH也可以
 | next: | …
 |  | …
 | CODE1 | ENDS

例6.6 编写一程序段，计算下列函数值。其中：变量X和Y是有符号字变量。

解：
 | DATA1 | SEGMENT
 |  | …
 |  | X | DW ?
 |  | Y | DW ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, X
 |  | CMP | AX, 0
 |  | JGE | case23
 |  | ADD | AX, 10 | ;第一种情况的计算结果
 |  | JMP | result
 | case23: | CMP | AX, 10D
 |  | JG | case3
 |  | MOV | BX, 30D
 |  | IMUL | BX | ;第二种情况的计算结果
 |  | JMP | result
 | case3: | SUB | AX, 9 | ;第三种情况的计算结果
 | result: | MOV | Y, AX | ;把计算结果保存到变量Y中
 |  | …
 | CODE1 | ENDS


6.2.2 分支结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 分支结构

例6.7 把下列C语言的语句改写成等价的汇编语言程序段(不考虑运算过程中的溢出)。

If (a+b > 0 && c%2 == 0) a = 62;
else a = 21;

其中：变量a，b和c都是有符号的整型(int)变量。

解：
 | DATA1 | SEGMENT
 |  | …
 |  | A | DW ?
 |  | B | DW ?
 |  | C | DW ?
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, A
 |  | ADD | AX, B
 |  | JLE | _ELSE | ;ADD指令会改变算术标志位
 |  | TEST | C, 1 | ;C%2==0，也就是：看C的最低位是否为0
 |  | JNZ | _ELSE
 |  | MOV | A, 62D
 |  | JMP | NEXT
 | _ELSE: | MOV | A, 21D
 | NEXT: | …
 | CODE1 | ENDS

例6.8 用地址转移表实现下列C语言的switch语句，其中：变量A和B是有符号的整型(int)变量。

switch | (a%8)
{case 0: | b = 32;
 | break;
case 1:
case 2: | b = a + 43;
 | break;
case 3: | b = 2*a;
 | break;
case 4: | b--;
 | break;
case 5:
case 6:
case 7: | printf(“Function 5_6_7”);
 | break;
} |
解：
 | DATA1 | SEGMENT
 |  | …
 |  | A | DW ?
 |  | B | DW ?
 |  | Table | DW case0. case12, case12, case3
 |  |  | DW case4, case567, case567, case567
 |  | MSG | DB 'Function 5_6_7$'
 |  | …
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | …
 |  | MOV | AX, A
 |  | MOV | BX, AX
 |  | AND | BX, 7 | ;得到BX的低三位，实现a%8的计算
 |  | SHL | BX, 1 | ;由于地址表是字类型，其下标要乘2
 |  | JMP | Table[BX] | ;利用地址表实现多路转移
 | case0: | MOV | B, 32D
 |  | JMP | next
 | case12: | ADD | AX, 43D
 |  | MOV | B, AX
 |  | JMP | next
 | case3: | SHL | AX, 1
 |  | MOV | B, AX
 |  | JMP | next
 | case4: | DEC | B
 |  | JMP | next
 | case567: | LEA | DX, MSG
 |  | MOV | AH, 9
 |  | INT | 21H
 |  | JMP | next
 | next: | …
 | CODE1 | ENDS

用地址表实现多路转移的关键在于：转移入口的地址表和转移情况可整数化。如果这二个要求有一个不满足，或很难构造，则无法使用该方法。


6.2.2 用伪指令实现分支结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 伪指令实现的分支结构

二、用伪指令实现的分支结构

为了改善汇编语言源程序的结构，减少显式转移语句所带来混乱，在宏汇编MASM 6.11系统中，增加了表达分支结构的伪指令。该伪指令的书写格式与高级语言的书写方式相类似，汇编程序在汇编时会自动增加转移指令和相应的标号。理解并掌握该知识，对将来学习《编译原理》课程也有一定的帮助。

分支伪指令的具体格式如下：

格式1：

.IF condition　　　　;以英文“句号”开头
   指令序列　　　　 ;条件"condition"成立时所执行的指令序列
.ENDIF

格式2：

.IF condition
   指令序列1
.ELSE
   指令序列2　　　　;条件"condition"不成立时所执行的指令序列
.ENDIF

格式3：

.IF condition1
   指令序列1
.ELSEIF condition2
   指令序列2　　　　;条件"condition2"成立时所执行的指令序列
.ENDIF

其中：条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相似，也可用括号来组成复杂的条件表达式。

条件表达式中可用的操作符有：==(等于)、!=(不等)、>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、&(位操作与)、!(逻辑非)、&&(逻辑与)、||(逻辑或)等。

若在条件表达式中检测标志位的信息，则可以使用的符号名有：CARRY?(相当于CF==1)、OVERFLOW?(OF==1)、PARITY?(PF==1)、SIGN?(SF==1)、ZERO?(ZF==1)等。例如：

.IF　CARRY? && AX != BX　　　　;检测CF==1且AX!=BX是否成立
    ;汇编语言指令序列
.ENDIF

在指令序列中，还可再含有其它的.IF伪指令，即：允许嵌套。伪指令.ELSEIF引导出另一个二叉分支，但它不能作伪指令块的第一个伪指令。

汇编程序在对“条件表达式”进行代码转换时将进行代码优化处理，以便尽可能生成最好的指令代码。如：

.IF　ax == 0

汇编程序会把它转换为指令“OR　ax, ax”，而不是“CMP　ax, 0”，因为前者比后者更好，而不是简单直接地转换为后者。

如果用伪指令来书写分支结构，那么，例6.5的代码段部分就可写成如下程序段：

…
MOV | AL, CHAR1
.IF AL>='a' && AL<='z' |  | ;语句象C语言语句吗？
SUB CHAR1, 20H
.ENDIF
… |  |

也可把例6.6的代码段部分就可写成如下程序段：

…
MOV AX, X
.IF AX < 0
ADD AX, 10 | ;计算第一种情况的结果
.ELSEIF AX <= 10
MOV BX, 30D IMUL BX | ;计算第二种情况的结果
.ELSE
SUB AX, 9 | ;计算第三种情况的结果
.ENDIF
MOV Y, AX … | ;把计算结果保存到变量Y中

例6.9 根据当前计算机的时间和日期，显示上午(AM)或下午(PM)，以及所在的季节。

解：显示解答


6.2.3 循环结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 循环结构

6.2.3 循环结构

循环结构是一个重要的程序结构，它具有重复执行某段程序的功能。通常，循环结构包括以下四个组成部分：

1、循环初始化部分——初始化循环控制变量、循环体所用到变量；
2、循环体部分——循环结构的主体；
3、循环调整部分——循环控制变量的修改、或循环终止条件的检查；
4、循环控制部分——程序执行的控制转移。

以上四部分可以在程序中用各种不同的形式体现出来，有时也并非清析地表达出来。常用的循环结构如图6.3所示。

(a)、Do—While结构 | (b)、While结构
图6.3 常用的循环结构示意图

一、用循环指令构成循环结构

在编写循环结构的程序片段时，我们可以多种方法来循环结构。如：循环次数是已知的，可用LOOP指令来构造循环；当循环次数是未知或不定的，则可用条件转移或无条件转移来构成循环结构。

例6.10 分类统计字数组data中正数、负数和零的个数，并分别存入内存字变量Positive、Negative和Zero中，数组元素个数保存在其第一个字中。

解：显示解答

例6.11 计算数组score的平均整数，并存入内存字变量Average中，数组以-1为结束标志。

解：
 | DATA1 | SEGMENT
 |  | data | DW 90, 95, 54, 65, 36, 78, 66, 0, 99, 50, -1
 |  | Average | DW 0
 | DATA1 | ENDS
 | CODE1 | SEGMENT
 |  | ASSUME CS:CODE1, DS:DATA1
 | START: | MOV | AX, DATA1
 |  | MOV | DS, AX
 |  | XOR | AX, AX
 |  | XOR | DX, DX | ;用(DX，AX)来保存数组元素之和
 |  | XOR | CX, CX | ;用CX来保存数组元素个数
 |  | LEA | SI, data | ;用指针SI来访问整个数组
 | again: | MOV | BX, word ptr [SI]
 |  | CMP | BX, 0
 |  | JL | over
 |  | ADD | AX, BX
 |  | ADC | DX, 0 | ;把当前数组元素之值加到(DX，AX)中
 |  | INC | CX | ;数组元素个数加1
 |  | ADD | SI, 2
 |  | JMP | again
 | over: | JCXZ | exit | ;防止零作除数，即数组是空数组
 |  | DIV | CX
 |  | MOV | Average, AX
 | exit: | MOV | AX, 4C00H
 |  | INT | 21H
 | CODE1 | ENDS
 |  | END | START


6.2.3 伪指令实现的循环结构

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 伪指令实现的循环结构

二、用伪指令实现的循环结构

在宏汇编MASM 6.11系统中，还增加了表达循环结构的伪指令，以便更清晰地表达WHILE循环、REPEAT-UNTIL循环。另外，还增加两个辅助循环的伪指令。这些伪指令的书写格式和含义与高级语言中相应语句的书写格式和含义相一致，所以，这些伪指令是很容易掌握的，也是非常有用的。

循环伪指令的格式和含义如下：

1、WHILE型循环伪指令

.WHILE condition
    循环体的指令序列　　　　;条件"condition”成立时所执行的指令序列
.ENDW

其中：.ENDW与前面的.WHILE相匹配，它标志着其循环体到此结束。

如果条件表达式“condition”在循环开始时，就为“假”(false)，那么，该循环体一次也不会被执行。

2、REPEAT型循环伪指令

.REPEAT
   循环体的指令序列
.UNTIL condition 		.REPEAT
   循环体的指令序列
.UNTILCXZ [condition]

REPEAT型循环在执行完循环体后，才判定逻辑表达式condition的值。若该表达式的值为真，则终止该循环，并将执行伪指令.UNTIL[CXZ]后面的指令，否则，将向上跳转到伪指令.REPEAT之后的指令，为继续执行其循环体作准备。

如果.UNTILCXZ后面没有写逻辑表达式，那么，由.REPEAT－.UNTILCXZ所构成的循环与用LOOP指令所过程的循环是一致的，它们都是以“CX=0”为循环终止条件。

如果.UNTILCXZ后面书写了逻辑表达式，那么，该逻辑表达式的形式只能是：“EXP1==EXP2”或“EXP1!=EXP2”。所以，这时由“.REPEAT－.UNTILCXZ condition”所构成的循环就与用LOOPNE/LOOPE指令所过程的循环是一致的，它们都是以“condition || CX=0”为循环终止条件。

和高级语言的REPEAT型的循环一样，.REPEAT－.UNTIL[CXZ]的循环体也会至少被执行一次。

.WHILE－.ENDW和.REPEAT－.UNTIL[CXZ]的循环体内还可再含有循环伪指令，这样就构成了循环结构的嵌套。

汇编程序在生产指令代码时会进行代码优化，以便尽可能得到最优化的指令序列。


6.2.3 辅助循环伪指令

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 程序的基本结构 → 辅助循环伪指令

3、辅助循环伪指令

(1)、终止循环伪指令

.BREAK
.BREAK .IF condition

该伪指令用来终止包含它的最内层循环。前者是无条件终止循环，后者是仅当逻辑表达式condition为真时，才终止循环。

.WHILE 1 | .REPEAT
… .BREAK .IF condition … | … .BREAK .IF condition …
ENDW | .UNTIL 0

对于以上二个循环，如果没有指令来终止循环的话，它们都将进入死循环状态，但如果在该层循环体内，存在伪指令“.BREAK .IF condition”的话，那么，当逻辑表达式condition为真时，该循环就会被终止了。

(2)、循环继续伪指令

.CONTINUE
.CONTINUE　.IF condition

该伪指令用于直接跳转到包含它的最内层循环的计算循环条件表达式的代码处。前者是无条件转移到计算循环条件表达式的代码处，后者是仅当条件表达式condition为真时，才进行这样的跳转。

辅助循环伪指令.BREAK和.CONTINUE只能在伪指令.WHILE－.ENDW和.REPEAT－.UNTIL的循环体内使用。

例6.12 显示9个数字字母'1'~'9'，26个大写字母，和显示任意输入的数字字符，并用按“回车”键来结束本程序的运行。

解：

DATA1 | SEGMENT
 | MSG1 | DB 13, 10, "Iteration: "
 | NUM1 | DB '1', "$"
 | MSG2 | DB 13, 10, "Alphabet: $"
 | NUM2 | DB 'A', " $"
 | MSG3 | DB 13, 10, "Type digits, then press ENTER: $"
DATA1 | ENDS
CODE1 | SEGMENT
 | ASSUME CS:CODE1, DS:DATA1
START: | MOV | AX, DATA1
 | MOV | DS, AX
 | MOV | CX, 9
 | MOV | AH, 09H
 | MOV | DX, OFFSET MSG1
 | .REPEAT
 | INT 21H INC NUM1 |  | ;显示Iteration: 1,2,~,9
 | .UNTILCXZ
 | MOV | DX, OFFSET MSG2
 | INT | 21H | ;显示字符串"Alphabet:"
 | MOV | AH, 09H
 | MOV | DX, OFFSET NUM2
 | .REPEAT
 | INT 21H INC NUM2 |  | ;显示当前字母 ;当前字母向后移
 | .UNTIL NUM2 > 'Z' |  | ;显示整个大写字母表
 | MOV | AH, 09H
 | MOV | DX, OFFSET MSG3
 | INT | 21H
 | .WHILE 1 |  | ;循环条件为永真的循环
 | MOV AH, 07H INT 21H |  | ;不带回显地从键盘读一个字符
 | .BREAK .IF AL == 13 |  | ;如果输入“回车”键，则终止循环
 | .CONTINUE .IF (AL<'0') || (AL>'9') |  | ;如果字符不是数字字符，则继续循环
 | MOV DL, AL MOV AH, 02H INT 21H |  | ;显示所输入的数字字母
 | .ENDW
 | MOV | AX, 4C00H
 | INT | 21H
CODE1 | ENDS
 | END | START


6.3 段的基本属性

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 段的基本属性

6.3 段的基本属性

在通常情况下，一个复杂的应用程序会由若干个模块组成，一个模块又会含有多个段。而不同模块的段之间、同一模块的段之间往往存在某种联系，这种联系就要体现在段属性的说明上。

段定义的一般格式如下：
 | 段名 | SEGMENT [对齐类型] [组合类型] [类别]
 |  | …
 | 段名 | ENDS

段属性“对齐类型”、“组合类型”和“类别”要按此顺序说明，但这些可选项可根据需要选择书写。如果源程序中不指定某个属性，那么，汇编程序将使用该属性的缺省值。

程序中的段名可以是唯一的，也可以与其它段同名。在同一模块中，如果有二个段同名，则后者被认为是前段的后续，这样，它们就属同一段。

当同一模块出现二个同名段时，则后者的可选项属性要么与前者相同，要么不写其属性而选用前者的段属性。

例6.13 同段名的作用
 | DATA1 | SEGMENT ;第一个数据段
 |  | MSG | DB "Hello, "
 | DATA1 | ENDS
 | CODE1 | SEGMENT ;第一个代码段
 |  | ASSUME CS:CODE1, DS:DATA1
 | START: | MOV | AX, DATA1
 |  | MOV | DS, AX
 |  | MOV | DX, offset MSG
 |  | MOV | AH, 9
 |  | INT | 21H
 | CODE1 | ENDS
 | DATA1 | SEGMENT ;第二个数据段
 |  | DB "World.$"
 | DATA1 | ENDS
 | CODE1 | SEGMENT ;第二个代码段
 |  | MOV | AX, 4C00H
 |  | INT | 21H
 | CODE1 | ENDS
 |  | END | START
 |  | END

在上面的例子中，第二个数据段是第一个数据段的后续，汇编程序把它们是合二为一，上述的代码段也如此。

下面，详细说明段属性的含义及其作用。

6.3.1 对齐类型(ALIGN)

对齐类型表示当前段对起始地址的要求，连接程序(LINK.EXE)按表6.1的地址格式来定位段的起始地址。在进行段定位时，会根据其定位类型进行定位的，所以，各段之间就有可能出现一些空闲字节，即可能浪费几个字节单元。

段对齐类型PARA是一个适用于所有段类型的对齐类型，它也是缺省的对齐类型。对齐类型BYTE和WORD通常用于数据段的定位，对齐类型DWORD通常用于80386及其以后CPU代码段的定位。

表6.1 段对齐类型与段起始地址之间的对应关系

对齐类型 | 起始地址(二进制) | 功能说明 | 最多的空闲字节数
BYTE | xxxx xxxx xxxx xxxx xxxx | 下一个字节地址 | 0
WORD | xxxx xxxx xxxx xxxx xxx0 | 下一个字地址 | 1
DWORD | xxxx xxxx xxxx xxxx xx00 | 下一个双字地址 | 3
PARA | xxxx xxxx xxxx xxxx 0000 | 下一个节地址 | 15
PAGE | xxxx xxxx xxxx 0000 0000 | 下一个页地址 | 127

6.3.2 组合类型(COMBINE)

组合类型是告诉连接程序如何把不同模块中段名相同的段合并在一起。具体的组合类型如下：

NONE | 表示当前段在逻辑上独立于其它模块，并有其自己的基地址。NONE是缺省的组合类型。
PUBLIC | 表示当前段与其它模块中同段名的PUBLIC类型段组合成一个段。组合的先后次序取决于LINK程序中目标模块排列的次序。在组合时，后续段的起始地址要按其对齐类型进行定位，所以，同名段之间可能有间隔。
COMMON | 表示当前段与其它模块中同名段重叠，也就是说，它们的起始地址相同。最终段的长度是同名段的最大长度。由于段覆盖，所以，前一同名段中的初始化数据被后续段的初始数据覆盖掉。
STACK | 组合类型STACK表示当前段是堆栈栈，其组合情况与PUBLIC相同。
AT 数值表达式 | 该数值表达式是当前段所指定的绝对起始地址的段地址。


6.3.3 段的类别和段组

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 段的基本属性 → 类别

6.3.3 类别(CLASS)

类别是一个由程序员指定的用单引号括起来的字符串。如果一个段没有给出类别，那么，这个段的类别就为空。类别是用于段的分类，连接程序利用该类别来调整同名、同类别的段，并使它们相邻。典型的类别是"Data"和"Code"。如果指定某段的类别是"Code"，那么，该段最好是代码段，这样，有的调试程序(如：CodeView)就可以顺序工作。

例如：
 | DATA1 | SEGMENT WORD PUBLIC "Data"
 |  | …
 | DATA1 | ENDS

上述段定义说明了该段的起始地址是下一个字地址、组合类型为PUBLIC、段类别是"Data"。

6.3.4 段组(GROUP)

段组伪指令GROUP是用于把源程序模块中若干个段结合成一个组，并对该段组定义一个段组名。段组伪指令的格式如下：

段组名 GROUP 段名[, 段名, ……]

其中：段名之间要用逗号间隔，段名也可以用表达式“SEG 变量”或“SEG 标号”。

下面举例说明段组伪指令的使用方法和作用。

例6.12 段组的作用
方法1：用一个段寄存器对应二个数据段
 | DATA1 | SEGMENT |  | ;第一个数据段
 |  | b1 | DB 10h
 | DATA1 | ENDS
 | DATA2 | SEGMENT |  | ;第二个数据段
 |  | b2 | DB 23h
 | DATA2 | ENDS
 | CODE1 | SEGMENT
 |  | ASSUME CS:CODE1, DS:DATA1 |  | ;(1)
 | START: | MOV | AX, DATA1
 |  | MOV | DS, AX | ;(2)把数据段DATA1的段值赋给段寄存器DS
 |  | …
 |  | MOV | BL, b1 | ;(3)引用DS来访问DATA1中的变量b1
 |  | …
 |  | ASSUME DS:DATA2 |  | ;(4)
 |  | MOV | AX, DATA2
 |  | MOV | DS, AX | ;(5)把数据段DATA2的段值赋给段寄存器DS
 |  | …
 |  | MOV | AL, b2 | ;(6)引用DS来访问DATA2中的变量b2
 |  | …
 | CODE1 | ENDS
 |  | END | START

在上例中，语句(1)说明DS与DATA1建立联系，语句(2)对DS赋值，语句(3)用DS来访问DATA1段的变量名。语句(4)说明DS与DATA2建立联系，语句(5)对DS赋值，语句(6)用DS来访问DATA2段的变量名。

在该例子中，因为只使用一个段寄存器DS来对应二个数据段，所以，需要切换DS的对应关系(如：语句(4))。但我们也可以用段寄存器DS和ES来分别对应段DATA1和DATA2，这样，方法1就可变成方法2。

方法2：用二个段寄存器对应二个数据段
 | DATA1 | SEGMENT
 |  | b1 | DB 10h
 | DATA1 | ENDS
 | DATA2 | SEGMENT
 |  | b2 | DB 23h
 | DATA2 | ENDS
 | CODE1 | SEGMENT
 |  | ASSUME CS:CODE1, DS:DATA1, ES:DATA2
 | START: | MOV | AX, DATA1
 |  | MOV | DS, AX | ;把数据段DATA1的段值赋给段寄存器DS
 |  | MOV | AX, DATA2
 |  | MOV | ES, AX | ;把数据段DATA2的段值赋给段寄存器ES
 |  | …
 |  | MOV | BL, b1 | ;引用DS来访问DATA1中的变量b1
 |  | …
 |  | MOV | AL, b2 | ;引用ES来访问DATA2中的变量b2
 |  | …
 | CODE1 | ENDS
 |  | END | START

我们还可以用段组来简化段寄存器的使用，把段DATA1和DATA2组成一个数据段。所以，把方法2再改写成方法3的形式。

方法3：用一个段组组成二个数据段
 | GSEG | GROUP | DATA1, DATA2 | ;把段DATA1和DATA2定义成一个段组
 | DATA1 | SEGMENT
 |  | b1 | DB 10h
 | DATA1 | ENDS
 | DATA2 | SEGMENT
 |  | b2 | DB 23h
 | DATA2 | ENDS
 | CODE1 | SEGMENT
 |  | ASSUME CS:CODE1, DS:GSEG
 | START: | MOV | AX, GSEG
 |  | MOV | DS, AX | ;把段组GSEG的段值赋给段寄存器DS
 |  | …
 |  | MOV | BL, b1 | ;引用DS来访问DATA1中的变量b1
 |  | …
 |  | MOV | AL, b2 | ;引用DS来访问DATA2中的变量b2
 |  | …
 | CODE1 | ENDS
 |  | END | START

定义段组后，段组内各段所定义的标号和变量，除了与定义它们的段起始点相关外，还与段组的起始点相关。规定如下：

、 | 如果在ASSUME伪指令中说明段组与段寄存器相对应，那么，有关标号或变量的偏移量就相对于段组起点计算；
、 | 如果在ASSUME伪指令中说明段组内的某各段与段寄存器相对应，那么，有关标号或变量的偏移量就相对于该段的起点。

所以，在使用段组后，程序员要谨慎使用ASSUME伪指令，并保证段寄存器的值与段组或段相一致。


6.4 简化的段定义

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 简化的段定义

6.4 简化的段定义

前面，我们介绍了完整的段定义格式，用完整的段定义格式虽然可以控制段的各种属性，但程序员很少会这样做。现在的汇编程序提供了一种简化的段定义方式，它使定义段更简单、方便。

6.4.1 存储模型说明伪指令

在使用简化的段定义方式之前，必须使用存储模式说明伪指令来描述源程序所采用的存储模式。该伪指令说程序所使用的存储模式，汇编程序将用该存储模式生成相应的ASSUME和GROUP语句，同时也为其它的简化段创建等价的预定义。

程序存储模式说明伪指令的格式如下：

.MODEL 存储模式[，语言类型] [，操作系统类型] [，堆栈类型]

程序可选的存储模式有：TINY、SMALL、COMPACT、MEDIUM、LARGE、HUGE和FLAT。

伪指令.MODEL必须写在源程序的首部，且只能出现一次，其前内容只能是注释。

如果用伪指令来指定程序所遵循的语言类型，那么，将不允许子程序的嵌套定义。与子程序定义有关的内容请见第7.5节。

一、存储模式

如果要用汇编语言编写被高级语言调用的子程序，那么，该汇编程序的存储模式必须与该高级语言编译(或解释)程序所使用的存储模式相匹配。汇编语言程序所能使用的存储模式、符号及其相关信息如表6.2所列。

在程序中，还可伪指令OPTION SEGMENT和SEGMENT来指定段的规模。

有关存储模式的具体规定如下：

、TINY

在汇编程序MASM 6.11和TASM 4.0，该存储类型是为编写COM文件类型而设置的。程序员还可用汇编命令行选项/AT和连接命令选项/TINY来达到此目的。

表6.2 存储模式的符号及其相关含义

代码的位距 | 数据的位距 | 段的宽度 | 数据段和代码段能否合并
Code Distance | Data Distance | Segment Width | Data & Code Combined?
Tiny
Small

Compact

Medium
Large
Huge
Flat
NEAR | NEAR | 16-bit | Yes
NEAR | NEAR | 16-bit | No
NEAR | FAR | 16-bit | No
FAR | NEAR | 16-bit | No
FAR | FAR | 16-bit | No
FAR | FAR | 16-bit | No
NEAR | NEAR | 32-bit | Yes

、SMALL

所有的数据变量必须在一个数据段之内，所有的代码也必须在一个代码段之内。在这种模型下，数据段寄存器的内容保持不变，所有转移也都是段内转移。

该存储类型是独立汇编语言源程序常用的存储模型。

、MEDIUM

所有的数据变量必须在一个数据段之内，但代码段可以有多个。在这种模型下，数据段寄存器的内容保持不变，转移可以是段间转移。

、COMPACT

数据段可以有多个，但代码段只能有一。

、LARGE

数据段和代码段都可以有多个，但一个数组的字节数不能超过64KB。

、HUGE

数据段和代码段都可以有多个，一个数组的字节数也可以超过64KB。

、FLAT

FLAT存储模式在创建执行文件时，将使该程序仅含一个包括程序数据和代码的32位段，并且只能在80386及其以后的计算机系统中运行。该程序的文件类型为EXE。

在使用该存储模式之前，必须先用伪指令.386、.486或其它伪指令来说明更高性能的CPU类型。也就是说：FLAT模式仅在386及其以后CPU模式下才能使用。

在该程序中，所有代码和数据位距的缺省值都是NEAR，子程序的类型也是NEAR，并且标识符@CodeSize，@DataSize和@Model的值分别为：0、0和7。

在FLAT存储模式下，程序将不使用段寄存器FS和GS。汇编程序在处理说明语句“.MODEL FLAT”时，将自动生成下列段寄存器说明语句：

ASSUME CS:FLAT, DS:FLAT, SS:FLAT, ES:FLAT, FS:ERROR, GS:ERROR

当然，程序员也可把该段寄存器说明语句写在其指令序列之中。

二、语言类型

其详细说明请见7.5.3节中所述。

三、操作系统类型

OS_DOS是当前唯一支持的选项值，也是该选项的缺省值。

四、堆栈类型

堆栈类型的值主要影响伪指令.STARTUP所生成的指令序列。该选项有二个可选值：NEARSTACK和FARSTACK。其中：NEARSTACK是该选项的缺省堆栈类型。

、NEARSTACK——堆栈段和数据段是同一段；

、FARSTACK——堆栈段和数据段是不同的段，且堆栈不在段组DGROUP中。

例如：

.MODEL SMALL, C, OS_DOS, FARSTACK


6.4.2 段简化定义伪指令

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 简化的段定义 → 段简化定义伪指令

6.4.2 简化段定义伪指令

简化段定义伪指令在说明一个新段即将开始的同时，也说明了上一个段的结束。在本段定义结束时，也不必用伪指令“ENDS”来标识。

具体的伪指令说明形式及其功能描述如下：

1、代码段定义

.CODE

作用：说明其下面的内容是代码段中内容。

2、堆栈段定义

.STACK [堆栈字节数]

其中，“堆栈字节数”可以不写，其缺省值为1024B。

3、数据段定义

.DATA / .DATA? / .CONST

作用：说明其下面的内容是数据段中的变量定义。

在一个源程序中，可以有多个伪指令.DATA定义的数据段，这就好象在源程序中定义多个同段名的数据段一样。

伪指令.DATA?说明下面是一个未初始化数据段的开始，伪指令.CONST说明下面是一个常数数据段的开始。这二条伪指令很少使用，除非在与高级语言编写的程序相结合时，为了遵守高级语言的某些约定，而需要特殊说明时才使用。

汇编程序在处理简化的堆栈段和数据段定义时，它会自动地把伪指令.STACK、.DATA、.DATA?和.CONST所定义的段组合成一个段组。如果想定义一个独立的、不与其它段组合在一起的数据段的话，那么，就可选用下面的数据段定义方式。

4、远程数据段定义

.FARDATA [段名] / .FARDATA? [段名]

其中：“段名”是可选项，如果不指定的话，则该段名就取其缺省段名。

作用：说明一个独立的数据段。

伪指令.FARDATA?说明下面是一个未初始化的、独立数据段的开始。通常情况下，很少使用该伪指令。


6.4.3 简化段段名的引用

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 简化的段定义 → 简化段段名的引用

6.4.3 简化段段名的引用

当使用简化的段定义时，一般情况下，程序员可以不知道这些段的段名、段地址堆齐类型和组合类型等。但当把简化定义的段和标准定义的段混合使用时，就需要知道简化定义段的基本属性。表6.3是在小模式下段的基本属性对应表。

表6.3 小模式下简化段定义的缺省属性表

伪指令 | 缺省段名 | 对齐类型 | 组合类型 | 类别 | 段组名
.CODE | _TEXT | WORD | PUBLIC | 'CODE' |
.FARDATA | FAR_DATA | PARA | NONE | 'FAR_DATA' |
.FARDATA? | FAR_BSS | PARA | NONE | 'FAR_BSS' |
.STACK | STACK | PARA | STACK | 'STACK' | DGROUP
.DATA | DATA | WORD | PUBLIC | 'DATA' | DGROUP
.DATA? | BSS | WORD | PUBLIC | 'BSS' | DGROUP
.CONST | CONST | WORD | PUBLIC | 'CONST' | DGROUP

在其它存储模型下，由伪指令".CODE"说明的代码段段名在"_TEXT"之前还要加上其模块名(源程序名)。假设，某模块名为ABC，则其缺省的代码段段名就为ABC_TEXT。因此，在这种情况下，程序的模块名或源程序名不要以数字开头。

例6.15 简化段定义的方法

.MODEL SMALL
.STACK 128
.DATA
 | MSG | DB "Simplified Segment Directives.$"
.CODE
 | MOV | AX, @DATA | ;取数据段的段值
 | MOV | DS, AX | ;把给段寄存器DS赋值
 | MOV | DX, offset MSG
 | MOV | AH, 9H
 | INT | 21h
 | MOV | AX, 4C00H
 | INT | 21h
 | END

另外，在汇编程序MASM中，还提供了二组简化的代码伪指令：.STARTUP和.EXIT。

、.STARTUP——在代码段的开始，用于自动初始化寄存器DS、SS和SP；
、.EXIT——用于结束程序的运行，它等价于下列二条语句：

MOV　AH, 4CH
INT　　21h

当使用汇编程序TASM时，以上二条伪指令分别改为：STARTUPCODE和EXITCODE。假设使用汇编程序MASM，那么，例6.15可改写成例6.16的形式。

例6.16

.MODEL SMALL
.STACK 128
.DATA
 | MSG | DB "Simplified Segment Directives.$"
.CODE
 | .STARTUP ;自动初始化寄存器DS、SS和SP
 | MOV | DX, offset MSG
 | MOV | AH, 9H
 | INT | 21h
 | .EXIT
 | END


6.5 源程序的辅助说明伪指令

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 辅助说明伪指令

6.5 源程序的辅助说明伪指令

除了以上一些使用率较高的伪指令外，还有一些使用频率不太高的其它伪指令。下面仅列举几个这样的伪指令。

6.5.1 模块名定义伪指令NAME

模块名定义伪指令NAME说明该源程序的模块名。该伪指令的一般格式如下：

NAME [模块名字符串]

6.5.2 页面定义伪指令PAGE

在源程序的开始，可用伪指令PAGE说明每页的最大行数、每行的字符数。该伪指令的一般格式为：

PAGE [[行数], 宽度]

其中：“行数”的取值范围为[10, 255]，“宽度”的取值范围为[60, 132]。

如：伪指令“PAGE 60, 80”说明每页最多有60行，每行最多有80个字符。

如果要在某指定行之后强行换页的话，那么，可在该行的下面书写不带操作数的伪指令PAGE。

6.5.3 标题定义伪指令TITLE

标题定义伪指令TITLE说明打印的标题，该标题可有60个字符。该伪指令在源程序头部只能书写一次，其一般格式如下：

TITLE [标题字符串]

如果程序中没有使用NAME伪操作，则汇编程序将用“标题字符串”的前六个字符串作为其模块名。如果程序中既无NAME伪操作，也无TITLE伪指令，那么，源文件名将作为模块名。

在汇编程序TASM环境下，标题定义伪指令是%TITLE。

6.5.4 子标题定义伪指令SUBTTL/SUBTITLE

子标题定义伪指令SUBTTL/SUBITLE说明打印页上的子标题，该子标题也可有60个字符，它在每页的第三行打印。该伪指令的一般格式如下：

SUBTTL/SUBTITLE [标题字符串]


6.6 习题

1 段的定义
1 段的定义
2 段寄存器的说明语句
2 段寄存器的说明语句
3 堆栈段的说明
3 堆栈段的说明
4 源程序的结构
4 源程序的结构
1 顺序结构
1 顺序结构
2 分支结构
2 分支结构
3 循环结构
3 循环结构
1 对齐类型
1 对齐类型
2 组合类型(COMBINE)
2 组合类型(COMBINE)
3 类别(CLASS)
3 类别(CLASS)
4 段组(GROUP)
4 段组(GROUP)
1 存储模型说明伪指令
1 存储模型说明伪指令
2 简化段定义伪指令
2 简化段定义伪指令
3 简化段段名的引用
3 简化段段名的引用
1 模块名定义伪指令NAME
1 模块名定义伪指令NAME
2 页面定义伪指令PAGE
2 页面定义伪指令PAGE
3 标题定义伪指令TITLE
3 标题定义伪指令TITLE
4 子标题定义伪指令SUBTTL
4 子标题定义伪指令SUBTTL
本章的学习内容和要求
本章的学习内容和要求
6.1 程序的基本组成
6.1 程序的基本组成
6.2 程序的基本结构
6.2 程序的基本结构
6.3 段的基本属性
6.3 段的基本属性
6.4 简化的段定义
6.4 简化的段定义
6.5 源程序的辅助说明伪指令
6.5 源程序的辅助说明伪指令
6.6 习题
6.6 习题

→ 习题

6.6 习题

6.1、简述ASSUME伪指令的作用，用该语句说明的段寄存器不用对其赋段地址的初值？

6.2、用二种定义方法堆栈段，并给出相应的初始化语句或说明。

6.3、把下列C语句的语句改写成功能相同的汇编语言程序片段(其中：变量都为整型变量)。

1)、h = (key & 0XFF00) >> 8;
2)、k = (k + '1' – 0xabcd) / 56;
3)、for (i = s = 0; i < 100; i++) s += data[i] * 2;
4)、for (s = 0, i = 100; i > 0; i--) s += i * 2;

6.4、把下列C语言的语句改写成等价的汇编语言程序段(不考虑运算过程中的溢出)。

If (a<1 || b/4 > 10 && c%8==5) {
  a = 20+b++;
  c <<= 2;
  }
else {a = 21-(++c);
     b--;
    }
其中：变量a、b和c都是有符号的整型(int)变量。

6.5、假设内存单元中有三个字a、b和c，编写一个程序，它可判断它们能否构成一个三角形，若能，CF为1，否则，CF为0。

6.6、假设有三个无符号字存放在以Buffer为开始的缓冲区中，编写一个程序把它们从低到高排序好。

6.7、编写一个程序，它把一位十六进制数转化成相应的数字字符或大写字母。

6.8、编写一个程序，它把一个合法的十六字符转化成相应的数值。

6.9、编写一个程序，它可统计32数DX:AX中二进制位是1的位数。

6.10、编写一个程序，它把CH和CL中的二进制位依次交叉存入AX中。

6.11、编写一个程序，求出从内存单元1000:0000开始的1024个字的32位累加和，并把该值存入程序中的变量Data中。

6.12、假设从变量Buff开始存放了200个字，编写一个程序统计出其正数、0和负数的个数，并把它们分别存入N1、N2和N3中。

6.13、用双重循环把下三角乘法表存入从product开始的45个字节中。

6.14、表示源程序结束的伪指令是什么？在其后所编写的指令在被汇编吗？

6.15、汇编语言程序一定会从代码段的第一条指令开始执行吗？如果不是，如何指定程序的入口地址？

6.16、编写一个程序，它把字符串String两端的空格删除(字符串以0结束)。

6.17、编写一个程序，它把字符串String中的数字字符删除掉(字符串以0结束)。

6.18、编写一个程序，它把AX的值以二进制字符串的形式存放在以buff开始的缓冲区中。

6.19、假设有一个以Data为首地址的字缓冲区，其缓冲区的大小在第一个字中，试编写一个程序，求出该缓冲区数值的最大值和最小值，并分别存入DI和SI中。

6.20、编写一个程序片段，把32位数DX:AX向左移六位。


第7章 子程序和库

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

第7章 子程序和库

子程序是程序设计所常见的基本概念，汇编语言也提供了编写子程序的方法。

本章主要介绍子程序的定义、调用和返回、子程序的参数传递等知识。此后，还将讲解如何构造自己的子程序库。

7.1 子程序的定义

如果某程序段在源程序内反复出现，那么，就可把该程序段定义为子程序。这样可以缩短源程序长度、节省目标程序的存储空间，也可提高程序的可维护性和共享性。

定义子程序的一般格式如下：
 | 子程序名 | PROC [NEAR | FAR]
 |  | … | ;子程序体
 | 子程序名 | ENDP

对子程序定义的具体规定如下：

、“子程序名”必须是一个合法的标识符，并前后二者要一致；
、PROC和ENDP必须是成对出现的关键字，它们分别表示子程序定义开始和结束；
、子程序的类型有近(NEAR)、远(FAR)之分，其缺省的类型是近类型；

、如果一个子程序要被另一段的程序调用，那么，其类型应定义为FAR，否则，其类型可以是NEAR。显然，NEAR类型的子程序只能被与其同段的程序所调用；

、子程序至少要有一条返回指令，也可有多条返回指令。返回指令是子程序的出口语句，但它不一定是子程序的最后一条语句；

、子程序名有三个属性：段值、偏移量和类型。其段值和偏移量对应于子程序的入口地址，其类型就是该子程序的类型。

编写子程序除了要考虑实现子程序功能的方法外，还要养成书写子程序说明信息的好习惯。其说明信息一般包括以下几方面内容：

、功能描述
、入口和出口参数
 | 、所用寄存器 | ;可选项，最好采用寄存器的保护和恢复方法，使之使用透明化
 | 、所用额外存储单元 | ;可选项，可以减少为子程序定义自己的局部变量
 | 、子程序的所采用的算法 | ;可选项，如果算法简单，可以不写
 | 、调用时的注意事项 | ;可选项，尽量避免除入口参数外还有其它的要求
 | 、子程序的编写者 | ;可选项，为将来的维护提供信息
 | 、子程序的编写日期 | ;可选项，用于确定程序是否是最新版本

这些说明性信息虽然不是子程序功能的一部分，但其他程序员可通过它们对该子程序的整体信息有一个较清晰认识，为准确地调用它们提供直接的帮助，与此同时，也为实现子程序的共享提供了必要的资料。


7.2 子程序的调用和返回

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的调用和返回指令

7.2 子程序的调用和返回指令

子程序的调用和返回是一对互逆操作，也是一种特殊的转移操作。

一方面，之所以说是转移，是因为当调用一个子程序时，程序的执行顺序被改变，CPU将转而执行子程序中的指令序列，在这方面，调用子程序的操作含有转移指令的功能，子程序的返回指令的转移特性与此类似；

另一方面，转移指令是一种“一去不复返”的操作，而当子程序完后，还要求CPU能转而执行调用指令之下的指令，它是一种“有去有回”的操作。

为了满足子程序调用和返回操作的特殊性，在指令系统中设置了相应的特定指令。

7.2.1 调用指令(CALL)

调用子程序指令的格式如下：

CALL　子程序名/Reg/Mem

子程序的调用指令分为近(near)调用和远(far)调用。如果被调用子程序的属性是近的，那么，CALL指令将产生一个近调用，它把该指令之后地址的偏移量(用一个字来表示的)压栈，把被调用子程序入口地址的偏移量送给指令指针寄存器IP即可实现执行程序的转移。近调用指令的堆栈操作如图7.1所示。

图7.1 近调用指令进栈操作示意图

如果被调用子程序的属性是远的，那么，CALL指令将产生一个远调用。这时，调用指令不仅要把该指令之后地址的偏移量压进栈，而且也要把段寄存器CS的值压进栈。在此之后，再把被调用子程序入口地址的偏移量和段值分别送给IP和CS，这样完成了子程序的远调用操作。远调用指令的堆栈操作如图7.2所示。

图7.2 远调用指令进栈操作示意图

子程序调用指令本身的执行不影响任何标志位，但子程序体中指令的执行会改变标志位，所以，如果希望子程序的执行不能改变调用指令前后的标志位，那么，就要在子程序的开始处保护标志位，在子程序的返回前恢复标志位。

例如：
CALL DISPLAY | ;DISPLAY是子程序名
CALL BX | ;BX的内容是子程序的偏移量
CALL WORD1 | ;WORD1是内存字变量，其值是子程序的偏移量
CALL DWORD1 | ;DWORD1是双字变量，其值是子程序的偏移量和段值
CALL word ptr [BX] | ;BX所指内存字单元的值是子程序的偏移量
CALL dword ptr [BX] | ;BX所指内存双字单元的值是子程序的偏移量和段值


7.2.2 子程序的返回指令

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的调用和返回指令 → 返回指令

7.2.2 返回指令(RET)

当子程序执行完时，需要返回到调用它的程序之中。为实现此功能，指令系统提供了一条专用的返回指令。其格式如下：

RET/RETN/RETF [Imm]

子程序的返回在功能上是子程序调用的逆操作。为了与子程序的远、近调用相对应，子程序的返回也分：远返回和近返回。返回指令在堆栈操作方面是调用指令的逆过程(如图7.3所示)。其具体规定如下：

、在近类型的子程序中，返回指令RET是近返回，其功能是把栈顶之值弹出到指令指针寄存器IP中，SP会被加2(如图7.3所示)；

、在远类型的子程序中，返回指令RET是远返回，其功能是：先弹出栈顶之值到IP中，再弹出栈顶之值到CS之中，SP总共会被加4(如图7.4所示)。

图7.3 近返回指令的出栈操作示意图

图7.4 远返回指令的出栈操作示意图

如果返回指令后面带有立即数(其值通常为偶数)，则表示在得到返回地址之后，SP还要增加的偏移量，它不是类似于高级语言中子程序的返回值(如图7.5所示)。

图7.5 带立即数的返回指令的出栈操作示意图

在MASM 5.0及其以后版本中，可用指令RETN或RETF来显式地告诉汇编程序是本子程序的返回是近返回，还是远返回。

例如：
RET | ;可能是近返回，也可能是远返回
RETN | ;近返回指令
RETF | ;远返回指令
RET 6 | ;子程序返回后，(SP)←(SP) + 6


7.2.2 子程序的举例

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的调用和返回指令 → 子程序举例

例7.1 编写一个子程序UPPER，实现把寄存器AL中存放的字符变大写。

解：
 | ;子程序功能：把AL中存放的字符变大写
 | ;入口参数：AL
 | ;出口参数：AL
 | ;算法描述：判断AL中字符必须在'a'~'z'之间才能把该字符变为大写
 | UPPER | PROC
 |  | CMP | AL, 'a' | ;书写'a'的ASCII码61H也可以
 |  | JB | over
 |  | CMP | AL, 'z'
 |  | JA | over
 |  | SUB | AL, 20H | ;书写指令AND AL, 0DFH也可以
 | over: | RET
 | UPPER | ENDP

例7.2 编写一个求字符串长度的子程序StrLen，该字符串以0为结束标志，其首地址存放在DS:DX，其长度保存在CX中返回。

解：
 | ;子程序功能：求字符串的长度
 | ;入口参数：DS:DX存放字符串的首地址，该字符串以0为结束标志
 | ;出口参数：CX存放该字符串的长度
 | ;算法描述：用BX来指针来扫描字符串中的字符，如果遇到其结束标志，则停止扫描字符串操作
 | StrLen | PROC
 |  | PUSH | AX
 |  | PUSH | BX | ;用堆栈来保存子程序所用到的寄存器内容
 |  | XOR | CX, CX
 |  | XOR | AL, AL
 |  | MOV | BX, DX
 | again: | CMP | [BX], AL
 |  | JZ | over
 |  | INC | CX | ;增加字符串的长度
 |  | INC | BX | ;访问字符串的指针向后移
 |  | JMP | again
 | over: | POP | BX | ;恢复在子程序开始时所保存的寄存器内容
 |  | POP | AX
 |  | RET
 | StrLen | ENDP


7.3 子程序的参数传递

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的参数传递

7.3 子程序的参数传递

子程序一般都是完成某种特定功能的程序段。当一个程序调用一个子程序时，通常都向子程序传递若干个数据让它来处理；当子程序处理完后，一般也向调用它的程序传递处理结果，我们称这种在调用程序和子程序之间的信息传递为参数传递。

用程序向子程序传递的参数称为子程序的入口参数，子程序向调用它的程序传递的参数称为子程序的出口参数。子程序的入口参数和出口参数都是任意项，对某个具体的子程序来说，要根据具体情况来确定其入口和出口参数，也可以二者都没有。

程序和被调用子程序之间的参数传递方法是程序员自己或和别人事先约定好的信息传递方法。这种信息传递方法可以是多种多样的，在本节，我们只介绍常用的、行之有效的参数传递方法有：寄存器传递参数、约定存储单元传递参数和堆栈传递参数等。如果对其它的参数传递方法感兴趣的话，可参考其它《汇编语言程序设计》书籍。

7.3.1 寄存器传递参数

一方面，由于CPU中的寄存器在任何程序中都是“可见”的，一个程序对某寄存器赋值后，在另一个程序中就能直接使用，所以，用寄存器来传递参数最直接、简便，也是最常用的参数传递方式。但另一方面，CPU中寄存器的个数和容量都是非常有限，所以，该方法适用于传递较少的参数信息。

例7.1是用寄存器传递参数的例子，子程序处理的数据被保存在寄存器AL中。假设有下列的程序段：

…
MOV | AL, 'b'
CALL | UPPER | ;子返回时，(AL)='B'
…
MOV | AL, '2'
CALL | UPPER | ;子返回时，AL的值不变，因为'2'不是字母
…

例7.3 按五位十进制的形式显示寄存器BX中的内容，如果BX的值小于0，则应在显示数值之前显示负号'-'。

例如：(BX)=123，显示：00123；(BX)=-234，显示：-00234；

解：
 | ;子程序功能：把寄存器BX的内容按十进制有符号数显示出来
 | ;入口参数：BX
 | ;出口参数：无，只有显示信息
 | ;算法描述： | 1、定义6个字节的存储单元 2、先判断BX是否小于零，如果是，则先显示负号'-'，再取BX的绝对值； 3、采用除10，得余数的方法，从低位向高位求出每位十进制位； 4、输出数据的字符串。
 | SubData | SEGMENT
 |  | DB | 5 DUP('0'), 0ah, 0dh, '$' ;0ah、0dh：换行、回车
 | SubData | ENDS
 | DISPBX | PROC
 |  | ASSUME | DS:SubData
 |  | PUSH | DS
 |  | PUSH | DX
 |  | PUSH | CX
 |  | PUSH | AX
 |  | MOV | AX, SubData | ;取子程序所用的数据区段地址
 |  | MOV | DS, AX
 |  | CMP | BX, 0
 |  | JGE | next
 |  | MOV | DL, '-'
 |  | MOV | AH, 2
 |  | INT | 21H | ;显示负号'-'
 |  | NEG | BX | ;求-BX，使其值为正数
 | next: | MOV | SI, 4
 |  | MOV | AX, BX
 |  | MOV | CX, 10D
 | again: | XOR | DX, DX
 |  | IDIV | CX | ;DX存放余数，AX存放商
 |  | ADD | DL, '0'
 |  | MOV | [SI], DL
 |  | DEC | SI
 |  | JGE | again
 |  | XOR | DX, DX
 |  | MOV | AH, 9
 |  | INT | 21H | ;调用中断21的功能9，显示DS:DX指向的字符串
 |  | POP | AX
 |  | POP | CX
 |  | POP | DX
 |  | POP | DS
 |  | RET
 | DISPBX | ENDP


7.3.2 存储单元传递参数

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的参数传递 → 存储单元传递参数

7.3.2 约定存储单元传递参数

在调用子程序时，当需要向子程序传递大量数据时，因受到寄存器容量的限制，就不能采用寄存器传递参数的方式，而要改用约定存储单元的传送方式。这种参数传递方式有点象情报人员和联络人员之间的传递信息方式，一个向指定地点放情报，另一个从指定地点取情报。

例7.2是采用约定存储单元传递参数的例子，所处理的数据不是直接传给子程序，而是把存储它们的地址告诉子程序。

例7.4：编写一个子程序分类统计出一个字符串中数字字符、字母和其它字符的个数。该字符串的首地址用DS:DX来指定(以0为字符串结束)，各类字符个数分别存放BX、CX和DI中。

解：
 | ;子程序功能：分类统计出字符串中数字字符、字母和其它字符的个数
 | ;入口参数：DS:DX指向被统计的字符串
 | ;出口参数：BX、CX和DI分别保存数字字符、字母和其它字符的个数
 | ;算法描述： | 1、当字符在'0'~'9'范围时，数字字符个数BX加1； 2、为了判断简单，先把字字母变成大写字母； 3、当字符在'A'~'Z'范围时，字母个数CX加1； 4、否则，其它字符个数DI加1。
 | COUNT | PROC
 |  | PUSH | AX
 |  | PUSH | SI
 |  | XOR | BX, BX
 |  | XOR | CX, CX
 |  | XOR | DI, DI | ;上三条指令使各类字符计数清零
 |  | MOV | SI, DX |
 | again: | MOV | AL, [SI]
 |  | INC | SI
 |  | CMP | AL, 0
 |  | JE | over
 |  | CMP | AL, '0'
 |  | JL | other
 |  | CMP | AL, '9'
 |  | JG | next
 |  | INC | BX | ;数字字符个数加1
 |  | JMP | again
 | next: | CALL | UPPER | ;调用子程序把AL中的字母变成大写字母
 |  | CMP | AL, 'A'
 |  | JL | other
 |  | CMP | AL, 'Z'
 |  | JG | other
 |  | INC | CX | ;字母个数加1
 |  | JMP | again
 | other: | INC | DI | ;其它字符个数加1
 |  | JMP | again
 | over: | POP | SI
 |  | POP | AX
 |  | RET
 | COUNT | ENDP

例7.5 显示出任意字符串中数字字符、字母和其它字符的个数。

解：
 | .MODEL | SMALL
 | .DATA
 | MSG | DB 'KSDJ L0984/[]3oiu OIU OIU (*&(5341', 0
 | .CODE
 |  | .STARTUP
 |  | LEA | DX, MSG | ;DS:DX指向待统计的字符串
 |  | CALL | COUNT | ;调用子程序统计出各类字符的个数
 |  | CALL | DISPBX | ;调用子程序显示数字字符的个数
 |  | MOV | BX, CX
 |  | CALL | DISPBX | ;调用子程序显示字母的个数
 |  | MOV | BX, DI
 |  | CALL | DISPBX | ;调用子程序显示其它字符的个数
 |  | .EXIT 0
 |  | END


7.3.3 堆栈传递参数

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的参数传递 → 堆栈传递参数

7.3.3 堆栈传递参数

堆栈是一个特殊的数据结构，它通常是用来保存程序的返回地址。当用它来传递参数时，势必会造成数据和返回地址混合在一起的局面，用起来要特别仔细。

具体做法如下：

(1)、当用堆栈传递入口参数时，要在调用子程序前把有关参数依次压栈，子程序从堆栈中取到入口参数；

(2)、当用堆栈传递出口参数时，要在子程序返回前，把有关参数依次压栈(这里还需要做点额外操作，要保证返回地址一定在栈顶)，调用程序就可以从堆栈中取到出口参数。

在通常情况下，我们用堆栈传入口参数，用寄存器传出口参数。

1、用堆栈传递入口参数的调用方法：

…
PUSH | Para1
…
PUSH | Paran | ;把n个字的参数压栈
CALL | SUBPRO | ;调用子程序SUBPRO
…

2、在子程序中取入口参数的方法：

、段内调用子程序

由于是段内调用，所以，CALL指令只把返回地址的偏移量(即IP的内容)压栈，如图7.6(a)所示。在进入子程序后，为了能读取传递过来的参数，需要用BP来访问堆栈，所以要先保护BP原来的值，再把当前SP的值传送给BP。

于是，当前BP所指向的堆栈单元与最后一个参数Paran之间隔着BP的原值和返回地址的偏移量，也就是说：二者之间相差4个字节。具体情况如图7.6(b)所示。
(a)、进入子程序时堆栈情况 | (b)、子程序寄存器保护后的堆栈情况
图7.6 在段内调用情况下子程序所能访问的堆栈情况

在子程序中读取用堆栈传递参数的一般方法如下程序片段所示。

SUBPRO | PROC NEAR
 | PUSH | BP | ;保护寄存器BP
 | MOV | BP, SP | ;用寄存器BP来访问堆栈，读取参数
 | … |  | ;保护其它寄存器的指令
 | MOV | Paran, [BP+4] | ;保护其它寄存器的指令
 | …
 | MOV | Para1, [BP+4+2*(n-1)]
 | …
SUBPRO | ENDP

、段间调用子程序

在段间调用子程序时，CALL指令会把返回地址的偏移量和段寄存器CS的内容都压栈，如图7.7(a)所示。在进入子程序后，与前面“段内调用子程序”一样，也需要用BP来读取传递过来的参数，所以，也要先保护BP原来的值，再把当前SP的值传送给BP。

这时，当前BP所指向的堆栈单元与最后一个参数Paran之间隔着BP的原值、返回地址的偏移量和段地址，所以，二者之间相差6个字节。具体情况如图7.7(b)所示。

(a)、进入子程序时堆栈情况 | (b)、子程序寄存器保护后的堆栈情况
图7.7 在段间调用情况下子程序所能访问的堆栈情况

在段间调用时，除了多一个返回段地址外，其它的内容与“段内调用”的情况完全一致，所以，在读取第i个参数时，只要用[BP+6+4*(n-i)]代替[BP+4+2(n-i)]即可(假设每个参数都是字类型)。


7.4 寄存器的保护与恢复

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 寄存器的保护与恢复

7.4 寄存器的保护与恢复

由于计算机的硬件资源只有一套，当子程序修改了寄存器的内容后，返回到调用它的程序时，这些寄存器的内容也就不会是调用子程序前的内容。这样，子程序修改寄存器内容就可能变成了调用它的副作用，这种副作用常常会导致调用程序的出错。为此，在编写子程序时，除了能对作为入口和出口参数的寄存器进行修改外，对其它寄存器的修改对调用程序来说都要是透明的，也就是说，在调用子程序指令的前后，除了作为入口和出口参数的寄存器内容可以不同外，其它寄存器的内容要保持不变。有时，也要求作为入口参数的寄存器内容保持不变。

在子程序中，保存和恢复寄存器内容的主要方法是：在子程序的开始把它所用到的寄存器压进栈，在返回前，再把它们弹出栈。这样编写的好处是该子程序可以被任何其它程序来调用。在调用指令前，不需要保存寄存器，在调用指令后，也无需恢复寄存器。

利用堆栈来保存和恢复寄存器内容方法的一般形式如下：

XXXXX | PROC
 | PUSH | REG1
 | …
 | PUSH | REGn | ;把子程序要使用的寄存器压栈，REGi代表某个寄存器
 | … … … |  | ;子程序的处理功能语句
 | POP | REGn | ;把前面压栈的寄存器弹出，注意它们的次序
 | …
 | POP | REG1
 | RET
XXXXX | ENDP

例7.2就是一个在子程序中利用堆栈来保存和恢复寄存器内容的例子。利用堆栈来实现此项功能时，应注意以下几点：

、用堆栈保存和恢复寄存器的内容，要注意堆栈“先进后出”的操作特点；
、通常情况下不保护入口参数寄存器的内容，当然，也可以根据事先的约定而对它们加以保护；
、如果用寄存器带回子程序的处理结果，那么，这些寄存器就一定不能加以保护；
、整个子程序的执行几乎肯定要改变标志位，可用PUSHF和POPF来保护和恢复标志位，但一般在子程序中不保护标志位，除非有此特殊需要；


7.5 子程序的完整定义

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义

7.5 子程序的完全定义

在7.1节所给出的子程序定义格式是一个最基本的、最简单的定义格式，它不能为子程序提供更简洁的调用方式。在宏汇编MASM 6.11系统中，为微机汇编语言的子程序提供了更加丰富的定义方式。

虽然子程序的这种定义方式显得稍微有点复杂，但它不仅为子程序的调用带来了极大的方便，而且其调用方式与高级语言中子程序的调用方式相一致，这就大大地降低了程序员熟练掌握它的难度。

7.5.1 子程序完全定义格式

子程序名 | PROC | [distance] [langtype] [visibility] [<prologuearg>]
 |  | [USES 寄存器列表] [，参数[:数据类型]]...
 | [LOCAL varlist]
 | 子程序的程序体
子程序名 | ENDP

定义子程序时，可使用参数表来直接指明其所要的参数，但程序员必须先用.MODEL伪指令，或使用<langtype>参数来说明本子程序所使用的程序设计语言类型。

程序员在定义子程序时，最好能象在高级语言(如：C/C++)定义过程那样，先说明该子程序的原型(用伪指令PROTO)，这样，在调用时，系统可以自动进行类型检查，也可以使用更方便的调用伪指令INVOKE来调用该子程序。有关子程序的原型说明伪指令和调用伪指令在随后第7.5.8和7.5.9小节中加以介绍。

子程序通常用RET指令来结束其执行，也可用指令“RET n”来指明在结束子程序执行后从堆栈弹出n个字节。有关返回指令请参阅7.2.2节中的叙述。

汇编程序在处理子程序时能自动产生“起始”代码(PROLOGUE Code)和“结束”代码(EPILOGUE code)。这两段特殊的代码分别完成：在调用子程序时，能把传递给子程序的参数压栈，在子程序结束时能把先前压栈的参数弹出。有了这两段代码，程序员在调用子程序时就不用自行考虑子程序的参数传递问题。

若子程序用指令RETN、RETF或IRETF作为子程序的结束指令，那么，汇编程序将不生成“结束”代码。

程序员可以用自己定义宏来替代缺省的“起始”和“结束”的代码段。这种替代方法是使用伪指令：OPTION PROLOGUE和OPTION EPILOGUE。

若子程序没有参数、局部变量，没使用USES子句，也不会产生新的段或段组，那么，子程序是可以嵌套定义的。程序员也可以使用返回指令RETN和RETF来避免子程序的嵌套。

在子程序内部，可以在指令之前使用伪指令LOCAL来说明其局部变量，有关规定在随后的第7.5.10节中有详细的说明。

下面就来介绍该定义格式中各个说明项的作用。


7.5.2 子程序的位距和语言类型

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 子程序的位距

7.5.2 子程序的位距

子程序的位距(Distance)有：Near、Far、Near16、Far16、Near32和Far32。

子程序位距描述符告诉汇编程序该子程序是在本段之内(Near)，还是在本段之外(Far)。Near和Far描述符表示使用当前的段规模(Segment Size)，Near16、Far16、Near32和Far32描述符是告诉汇编程序忽略当前的段规模，而使用指定16位或32位的段规模。

若选用类型Near或Far，那么，汇编程序将根据当前段的规模来决定选用16位，还是32位的Near或Far。

若程序员不指定该选项，那么汇编程序将根据当前的存储模式(由.MODEL来决定)和处理机类型来决定子程序类型。若不使用伪指令.MODEL，那么，Near是缺省的类型。

7.5.3 子程序的语言类型

子程序语言类型(Language Type)可以是任何一种有效的程序设计语句类型，由它来告诉汇编程序将使用什么样的标识符的命名风格、子程序的调用和返回约定。该语言类型说明可使汇编语言程序与其它语言程序达到共享的目的。所有有效的语言类型及其书写规定如表7.1所列。

表7.1 语言类型及其书写规定

 | C | SYSCALL | STDCALL | Basic | Fortran | Pascal
前导下划线 | X |  | X |  |  |
字母大写化 |  |  |  | X | X | X
参数从左到右 |  |  |  | X | X | X
参数从右到左 | X | X | X |  |  |
调用程序清空堆栈 | X |  | * |  |  |
保存指针寄存器BP |  |  |  | X | X | X
使用VARARG参数 | X | X | X |  |  |

*若使用:VARARG参数，则调用程序清空堆栈，否则，被调用的子程序清空堆栈。

程序员可用另外三种方法来设置程序的语言类型：.MODEL、OPTION LANGTYPE:和命令行选项/Gx。若在程序和命令行中都说明了语言类型，那么，前者的说明优先。

另外，程序员也可用命令行选项/H来限定标识符的最大长度。

例如：

Pascal语言风格:OPTION LANGUAGE:PASCAL、/Gc

C语言风格:OPTION LANGUAGE:C、/Gd


7.5.4 子程序的可见性和首尾宏操作

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 子程序的可见性

7.5.4 子程序的可见性

子程序的可见性(Visibility)决定该子程序对其它模块是否可用。它共有三个属性值：PRIVATE、PUBLIC和EXPORT。

PUBLIC属性是子程序标准的缺省属性，但该缺省属性可以用伪指令OPTION PROC来修改。EXPORT属性意味着该子程序是一个“远”的、具有PUBLIC属性的子程序，并要求连接程序在生成可执行文件时把其入口地址放入导出入口地址表中。

例如：

OPTION PROC : PRIVATE　　;说明子程序的可见性为：PRIVATE
OPTION PROC : EXPORT　　 ;说明子程序的可见性为：EXPORT

7.5.5 子程序的起始和结束操作

当程序员想用自己定义的宏来替代缺省的“起始”和“结束”的代码段时，可用下列说明语句来实现：

OPTION PROLOGUE : MacroName1
OPTION EPILOGUE : MacroName2

PROLOGUE和EPILOGUE分别指定MacroName1和MacroName2为“起始”和“结束”代码段的宏名。

汇编程序对用户定义的宏MacroName1和MacroName2的形式有较严格的规定，要求宏的定义形式如下：

MacroName MACRO ProcName, flags, argbytes, localbytes, <reglist>, userparms:VARARG

该宏定义的每个参数都有详细的说明，感兴趣的读者可看有关技术资料或MASM 6.11中的帮助，详细的说明在此从略，但建议使用缺省的宏。

如果想取消当前指定的宏名，而恢复使用缺省的“起始”和“结束”代码段的宏名，那么，可用下列说明语句，即指定二个缺省的宏名PrologueDef和EpilogueDef。

OPTION PROLOGUE : PrologueDef
OPTION EPILOGUE : EpilogueDef

若程序员不要汇编程序自动产生“起始”和“结束”代码，则可用NONE来代替说明语句中的宏名，即：

OPTION PROLOGUE : NONE
OPTION EPILOGUE : NONE


7.5.6 寄存器的保护与恢复

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 寄存器的保护与恢复

7.5.6 寄存器的保护和恢复

保护寄存器说明子句的说明格式：

USES 寄存器列表

该说明子句要求汇编程序为其生成保护和恢复寄存器的指令序列，即：在进入子程序执行指令之前，把寄存器列表中的寄存器压进堆栈，在结束子程序执行时，把先前压进堆栈的寄存器弹出，以达到保护寄存器的目的。

寄存器列表：列举出在子程序中需要保护的寄存器名，即：在子程序开始时需要把内容进栈的寄存器名。若有多个寄存器名，则在寄存器名之间要用“空格”来分开。

例如：

Dsip | PROC USES AX DX, FUNC:WORD, MSG:PTR BYTE
 | MOV | DX, MSG
 | MOV | AX, FUNC
 | INT | 21H
 | RET
Disp | ENDP

汇编程序在处理该子程序时，会根据子句USES的作用，在第一条指令“MOV DX, MSG”之前，插入把寄存器AX和DX进栈的指令序列，即：

PUSH　AX
PUSH　DX

而在返回指令RET之前插入把寄存器DX和AX的值弹出的指令序列，即：

POP　DX
POP　AX

注意：若子程序含有多个RET或IRET指令，那么，汇编程序在每个RET或IRET指令前都将增加相应的弹出堆栈指令序列。

从子句USES的功能来看，它与前面7.4节“寄存器的包含与恢复”中所用的方法完全一致，所不同的是：用USE子句进行寄存器保护和恢复的代码是由汇编程序自动产生的，程序员不用关心如何去做，有点象高级语言的编程风格，而7.4节中的代码则是由程序员自己来安排的。


7.5.7 子程序参数传递

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 子程序的参数传递

7.5.7 子程序的参数传递

子程序参数是用来向子程序传递信息的数据。若有多个参数，则参数之间要用逗号分割。为了能说明子程序的参数，程序员必须事先指定参数所遵循的语言类型或使用“语言类型”参数。

参数的数据类型可以是任何一个有效的数据类型说明符或VARARG。VARARG数据类型允许向子程序传递“个数”不定的参数，其参数之间要用逗号“，”来分开。

若参数表中含有VARARG说明的参数，那么，该参数一定是该子程序的最后一个参数。其规定隐含地说明了在参数表中只能有一个用VARARG说明的参数。

当子程序的语言类型是C、SYSCALL和STDCALL时，在其参数表中才能使用VARARG数据类型的参数。见前面的表7.1中所列。

如果没有显式地指定某个参数的数据类型，那么，在16位段规模的情况下，其缺省的数据类型是WORD；在32位段规模的情况下，其缺省的数据类型是DWORD。

7.5.8 子程序的原型说明

子程序原型的说明格式如下：

子程序名 PROTO [distance] [langtype] [，[parameter]:tag]...

该说明语句告诉汇编程序该子程序的若干属性，如：位距、语语言类型、参数个数及其类型等。这样，汇编程序就可以对其定义进行适当的检查。

如果对所有基于堆栈的过程都定义一个原型，那么，就可把这些原型存放在一个独立的包含文件(用伪指令INCLDUE来装入)中。使用这种方法对将来把所有子程序放入自定义的库文件中是非常方便的。

该原型说明语句中参数distance、langtype、parameter和tag等的含义与前面的叙述相一致，在此不再重复。


7.5.9 子程序调用伪指令

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 调用伪指令

7.5.9 子程序的调用伪指令

子程序调用伪指令INVOKE与子程序的调用指令CALL在功能上是一致的，但它使汇编语言的子程序调用方法高级语言化，程序员可不用理会一些调用细节问题。

调用伪指令INVOKE的使用格式如下：

INVOKE expression [, arguments]

其中：expression—地址表达式，通常为子程序名；
arguments—传递的各参数之间用逗号','分开，参数可以是寄存器、表达式或ADDR 标识符等。

该伪指令是调用基于堆栈的子程序的方法，它把所有参数压栈，子程序结束时，又把参数自动弹出堆栈。

在参数传递时，汇编程序将根据子程序的原型进行数据类型检查。若需要进行参数类型转换的话，汇编程序则会自动生成一段代码来满足其数据类型转换的要求。

例如：

INVOKE TEST, AX, 12+34, ADDR MSG

其中：TEST是子程序名，寄存器AX和表达式“12+34”是参数，“ADDR MSG”是传递变量MSG的地址。

例7.6 编写一个累加参数数值的子程序。其中参数的个数不定，参数的个数由第一个参数来确定。

解：

 | .MODEL SMALL .STACK 256 .CODE
 | ;第一个参数parmcount确定其后面参数parmvalues中所含参数的个数
ADDUP | PROC NEAR C, parmcount:WORD, parmvalues:VARARG
 | XOR | AX, AX
 | XOR | SI, SI
 | MOV | CX, parmcount
 | .REPEAT
 | ADD AX, parmvalues[SI] ADD SI, 2
 | .UNTILCXZ
 | RET
ADDUP | ENDP
 | .STARTUP
 | INVOKE | ADDUP, 3, 5, 2, 4 | ;调用子程序ADDUP，计算5+2+4
 | INVOKE | ADDUP, 4, 1, 2, 3, 4 | ;调用子程序ADDUP，计算1+2+3+4
 | .EXIT 0
 | .END


7.5.10 局部变量的定义

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序的完全定义 → 局部变量的定义

7.5.10 局部变量的定义

局部变量的定义格式：

LOCAL 变量名[[数量]] [:数据类型] [，变量名[[数量]] [:数据类型]]...

伪指令LOCAL的作用是说明一个或多个临时的局部变量(位于堆栈中)。局部变量必须在任何指令之前加以说明，并可用多个LOCAL伪指令来说明其局部变量。

在子程序中，若说明了某个局部变量，则子程序体中的指令就可使用该局部变量。汇编程序会把对它的引用转换成用指针寄存器BP来访问其在堆栈中的实际存储单元。

在局部变量的作用域与高级语言中局部变量的作用域相一致，即：局部变量只能在当前子程序中使用，离开该子程序，它们就不能再被引用。但在局部变量的命名规则上有所不同，高级语言中的局部变量可与外层变量同名，而汇编语言中的局部变量不能与其它任何变量同名，否则，在汇编时，将会给出“重定义”(Symbol redefinition)的错误信息。

“数量”用来说明该变量所具有的元素个数。象高级语言的数组定义一样，该数量必须写在括号“[ ]”之中。“数量”说明项是可选项。

局部变量的类型说明符可以是任何合法的数据类型说明符。在16位段环境下，该缺省的数据类型是WORD，而在32位段环境下，该缺省的数据类型是DWORD。

此处伪指令LOCAL的作用与9.3.1节中伪指令LOCAL的作用是完全不同的，具体的差异请见9.3.1节中的比较。

例如：

LOCAL　data[20]:BYTE, num:WORD

在上例的说明中，定义了二个局部变量：data和num。前者是字节类型，并有20个元素，后者是字类型，只有其自身1个元素。


7.5 子程序库

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序库

7.6 子程序库

库文件对学过C/C++语言程序设计的读者来说应该是不会陌生的，该语言的程序设计环境提供了大量的库文件，也就是说，提供了大量的标准函数或过程。在本节里，介绍读者如何创建自己的库文件。

7.6.1 建立库文件命令LIB

宏汇编MASM系统提供了建立库文件的命令文件LIB.EXE。其通常是在命令行环境(MS-DOS方式)下使用的，当然，也可在Windows 95/98等环境下利用其“开始”菜单下的“运行”功能项来使用。

一、MS-DOS系统

显示命令LIB用法的命令如下：

…>lib /?

该命令的显示结果如图7.9中所示。

二、Windows系统

图7.8 运行LIB命令的画面

图7.9 显示LIB命令功能的画面

命令LIB的使用方式和显示结果如图7.8和7.9所示。

三、命令显示内容的解释

1)、各选项的解释

选项 | 含义
/?、/HELP | 显示LIB命令的用法，描述各命令行参数的含义
/IGNORECASE | 忽略子程序名中的大小写 | 在实践中，作用不明显
/NOIGNORECASE | 不忽略子程序名中的大小写
/NOEXTDICTIONARY | 不建立扩展的目录
/NOLOGO | 不显示版本号和版权信息
/PAGESIZE:n | 设置库文件的每页字节数为n

2)、命令项的解释：

选项 | 含义
+name | 向库文件中加一个新的目标文件
-name | 从库文件中删除一个指定的目标文件
-+name | 用新的目标文件替换掉库文件中原有的目标文件
*name | 拷贝出指定的目标文件
-*name | 从库文件中移出指定的目标文件

在弄懂了LIB的各项功能含义后，读者就可根据自己的需要来建立库文件了。

7.6.2 建立库文件举例

假设现有目标文件sub1.obj、sub2.obj和sub3.obj，要用它们建立库文件mylib.lib。可用下列方法来建立该库文件：

方法1：所有目标文件都准备好了，可一次性把它们加入到库文件中

…>lib mylib +sub1 +sub2 +sub3

方法2：随着目标文件的逐个生成，而依次把它们加入到库文件中

…>lib mylib +sub1
…>lib mylib +sub2
…>lib mylib +sub3

假如源文件sub3.asm已修改，并也生成了新的目标文件sub3.obj，这时，就需要把库文件mylib.lib中的sub3.obj替换成新的目标文件。于是，可用下面命令来实现替换：

…>lib mylib -+sub3

当提示输入目标库文件名(Output library)时，可按“回车”用默认的原库文件名。

如果想查看库文件mylib.lib中各文件的大小和存放的先后次序，可用下列命令：

…>lib mylib, list　　　　　;把库文件mylib.lib中的文件结构生成到文件list中
…>type list


7.6.3 库文件的应用

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序库 → 库文件的应用

7.6.3 库文件的应用

在开发一个功能较弱的应用程序时，其执行文件通常可由一个目标文件连接而成，当开发一个功能较强、关系较复杂的应用程序时，其执行文件很难由一个目标文件连接而成，常常是由多个目标文件(模块)连接而成的。各模块之间无疑会存在着相互调用、相互访问数据单元等内在联系，各模块之间的相互联系就产生了这样的问题：程序员如何在源程序中来表达这种联系？

为了解决描述各模块之间的联系，汇编语言提供了二条伪指令PUBLIC和EXTRN，它们的作用有点象C/C++语言说明变量、过程和函数是“全局的”或“外部的”。

这二条伪指令的具体用法和含义如下：

1、伪指令PUBLIC

伪指令PUBLIC是用来说明：当前模块中哪些标识符是能被其它模块引用的公共标识符。其说明的一般格式如下：

PUBLIC　标识符1, 标识符2, ……

其中：“标识符”可以是变量名、过程名和程序标号，各标识符之间要用逗号分开。

上面说明语句说明了标识符1、标识符2等是公共标识符，可以被其它模块引用。在一个模块中，可用多条PUBLIC伪指令来说明公共标识符。

2、伪指令EXTRN

伪指令EXTRN是用来说明：在当前模块所使用的标识符中，哪些标识符是已在其它模块中被定义为指定类型的标识符。如果当前模块使用了其它模块的标识符，而对它又不加以说明的话，那么，在汇编时，汇编程序将会给出下列出错信息：

error nnnnn: undefined symbol : XXXXXX

其中：“nnnnn”是错误号，“XXXXXX”是当前模块中没有定义的标识符。

伪指令EXTRN的一般说明格式如下：

EXTRN　标识符1:类型1, 标识符2:类型2, ……

其中：“标识符”和“类型”之间要用冒号“:”连接。

上面语句说明了标识符1、标识符2等是外部标识符，它们在其它模块中已被分别定义为类型1、类型2等，该类型说明符可以是：NEAR、FAR、BYTE、WORD、DWORD等之一。如果在一条说明伪指令中说明了多个标识符，那么，各标识符之间要用逗号分开。

在一个模块中，可用多条EXTRN伪指令来说明本模块所引用的外部标识符。

注意：伪指令EXTRN中所说明的标识符必须在其定义的模块中被PUBLIC伪指令说明为公共标识符，并且其说明的标识符类型要与该标识符在定义是的类型相一致，否则，要么不能生成其可执行文件，要么其执行文件不能正确运行。


7.6.3 库文件的应用举例

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序库 → 库文件的应用举例

例7.7 把例7.3、7.4和7.5合并在一起生成一个可执行文件，假设它们所对应的源程序名分别为Count.ASM、DispBX.ASM和Main.ASM。

解：由于在源文件Count.ASM中调用了子程序UPPER，所以，例7.1的程序也必须加入到本题中。假设其源文件名为Upper.ASM。

由于生成本题的执行文件需要四个模块，模块之间存在着调用关系，所以，在有关源文件中需要说明某些标识符为外部属性，或说明其为公共属性。

为了把前面例子中的子程序改写成可汇编的程序，需要添加一些简单的说明语句或进行简单修改，其添加或改写的部分已在下面用“下划线”表示出来。
;源文件Upper.ASM
;子程序说明信息：……
PUBLIC | UPPER
SegUpr | SEGMENT 'code'
UPPER | PROC FAR
 | …… |  | ;例7.1中的程序段，在此从略
UPPER | ENDP
SegUpr | ENDS
 | END
;源文件DispBX.ASM ;子程序说明信息：……
PUBLIC | DISPBX
SubData | SEGMENT
 | DB | 5 DUP('0'), 0ah, 0dh, '$'
SubData | ENDS
SegDisp | SEGMENT 'code'
DISPBX | PROC FAR
 | …… |  | ;例7.3中的程序段，在此从略
DISPBX | ENDP
SegDisp | ENDS
 | END
;源文件Count.ASM ;子程序说明信息：……
PUBLIC | COUNT
EXTRN | UPPER:FAR
SegCount | SEGMENT 'code'
COUNT | PROC FAR
 | …… |  | ;例7.4中的程序段，在此从略
COUNT | ENDP
SegCount | ENDS
 | END
;源文件Main.ASM
EXTRN | COUNT:FAR, DISPBX:FAR
.MODEL | SMALL
.DATA
 | STR | DB 'KSDJ L0984/[]3oiu OIU OIU (*&(5341', 0
.CODE
 | .STARTUP
 | LEA | DX, STR
 | CALL | COUNT | ;调用子程序统计出各类字符的个数
 | CALL | DISPBX | ;调用子程序显示数字字符的个数
 | MOV | BX, CX
 | CALL | DISPBX | ;调用子程序显示字母的个数
 | MOV | BX, DI
 | CALL | DISPBX | ;调用子程序显示其它字符的个数
 | .EXIT | 0
 | END

经过以上改写后，可用下列命令把它们分别汇编成目标文件(假设已安装了MASM编程环境)：

…>MASM upper
…>MASM dispbx
…>MASM count
…>MASM main

有了这些目标文件后，可用以下二种方法来生成可执行文件。

方法1：把所有的目标文件连接在一起

…>link main+upper+count+dispbx

方法2：把目标文件upper.obj、count.obj和dispbx.obj加到自己开发的库文件中，然后在连接时，与该库文件连接。

…>lib mylib +upper +count +dispbx
…>link main
Microsoft (R) Segmented Executable Linker Version 5.31.009 Jul 13 1992
Copyright (C) Microsoft Corp 1984-1992. All rights reserved.
Run File [main.exe]:
List File [nul.map]:
Libraries [.lib]: mylib　　　　　　;输入要连接的库文件，可用加号“+”连接多个库文件
Definitions File [nul.def]:
LINK : warning L4021: no stack segment

…>main　　　　　　　　　　　　;运行生成的文件

以上各步骤也可由集成开发环境PWB来完成，具体介绍请见附录1。另外，当模块的指令条数较少时，也可以把几个子模块合在一个源文件中。


7.6.4 库文件的好处

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 子程序库 → 库文件的好处

7.6.4 库文件的好处

程序员在编写源程序时，通常采用模块化的思想来组织源程序：把各类不同的子程序分别编写在不同的源程序中，在各源程序中说明所用到的在其它模块中定义，或说明本模块的定义子程序可被其它模块调用。这样组织后，就可以分别汇编它们而得到其相应的目标文件，在有了这些目标文件后，就可生成最终的可执行文件，但可用不同的方法来生成最终的可执行目标文件。

方法1：直接连接目标文件而生成可执行文件(如上节例7.6中的方法1所示)

这种方法简单、方便，也是常用的一种方法，但在连接时，LINK程序会把目标文件中的所有代码都嵌入到执行文件中，从而使得：包含在某目标文件中、但并没有被调用的子程序代码也出现在执行文件中。这种情况无疑增加了执行文件的字节数。

方法2：采用子程序库的方法(如上节例7.6中的方法2所示)

库文件可以把它看成是子程序的集合。库文件中存储着子程序名、子程序的目标代码以及连接所需要的重定位信息。当某目标文件与库文件相连接时，LINK程序只把目标文件所用到的子程序从库文件中找出来，并合并到最终的可执行文件中，而不是把库中所含的全部子程序都纳入最后的可执行文件。

对照方法1和2可知：用库文件来存储子程序可生成较短的执行文件。


7.7 习题

1 调用指令
1 调用指令
2 返回指令
2 返回指令
1 寄存器传递参数
1 寄存器传递参数
2 约定存储单元传递参数
2 约定存储单元传递参数
3 堆栈传递参数
3 堆栈传递参数
1 子程序完全定义格式
1 子程序完全定义格式
2 子程序的位距
2 子程序的位距
3 子程序的语言类型
3 子程序的语言类型
4 子程序的可见性
4 子程序的可见性
5 子程序的起始和结束操作
5 子程序的起始和结束操作
6 寄存器的保护和恢复
6 寄存器的保护和恢复
7 子程序的参数传递
7 子程序的参数传递
8 子程序的原型说明
8 子程序的原型说明
9 子程序的调用伪指令
9 子程序的调用伪指令
10 局部变量的定义
10 局部变量的定义
1 建立库文件命令
1 建立库文件命令
2 建立库文件举例
2 建立库文件举例
3 库文件的应用
3 库文件的应用
4 库文件的好处
4 库文件的好处
本章的学习内容和要求
本章的学习内容和要求
7.1 子程序的定义
7.1 子程序的定义
7.2 子程序的调用和返回指令
7.2 子程序的调用和返回指令
7.3 子程序的参数传递
7.3 子程序的参数传递
7.4 寄存器的保护与恢复
7.4 寄存器的保护与恢复
7.5 子程序的完全定义
7.5 子程序的完全定义
7.6 子程序库
7.6 子程序库
7.7 习题
7.7 习题

→ 习题

7.7 习题

7.1、汇编语言的子程序是如何定义的？所使用的关键字有哪些？

7.2、为了编写具有良好风格的子程序，一般需要书写哪些重要的说明性信息？

7.3、简述调用指令CALL和转移指令JMP之间的主要区别。

7.4、简述段内和段间子程序调用指令CALL的主要区别。

7.5、子程序返回指令RET的功能能否用JMP指令来模拟，若可以，请用段内子程序的返回加以说明，否则，说明理由。

7.6、子程序返回指令“RET 6”的功能是返回数值“6”给调用程序吗？若不是，那其作用是什么？

7.7、在子程序中要使其所用寄存器对调用者是透明的，请举例说明达到其目的的方法。

7.8、编写子程序实现下列功能，参数的传递方式可自行决定(假设所有变量都是字类型)。

1)、ABS(x)=|x|
2)、f(x)=3x2 + 5x – 8
3)、strlen(String)　　　　//返回字符串String的长度，该字符串以0结束
4)、判断三条边a、b和c能否构成三角形，若能，CF为1，否则，CF为0

7.9、编写计算将CX值三次方的子程序，结果也存入CX(不考虑溢出问题)。

7.10、编写计算表达式AX=DI×SI／100H的子程序(不考虑溢出问题)。

7.11、编写计算EAX=EAX+EBX+ECX+EDX的子程序。在计算过程中，若产生进位，则EDI=1，否则，EDI=0。

7.12、在MASM 6.x编程环境下，简述子程序定义中USE说明语句的作用是什么？

7.13、调用子程序指令CALL和调用伪指令INVOKE的主要区别是什么？

7.14、在子程序的完全定义形式中，其语言类型所起的作用是什么？可用哪二种方法来设定子程序的语言类型？

7.15、如何指定子程序的传递参数是动态的，对动态参数有哪些规定？

7.16、编写一个子程序，其功能是把其所有参数数值之和存入AX中，每个参数都数16位二进制数，但个数不定。

7.17、子程序的参数传递有传值和传地址之分，在汇编语言中，如何实现传地址？请举例说明。

7.18、在高级语言中，子程序可定义其局部变量，在汇编语言中，能定义其局部变量吗？若能，请举例说明。

7.19、使用.REPEAT和.UNTIL语句编写一个子程序，其功能是从DS:SI开始的内存单元中查找是否有与AL相同的数值。若找不到，CF=0，否则，CF=1，且SI指向所找到位置。假设查找区域以0为结束标志。

7.20、用.REPEAT和.UNTILCXZ语句编写一个子程序，用以在内存缓冲区中填入00H。内存缓冲区的首址及长度分别由DS:SI和CX来确定。

7.21、用.WHILE和.ENDW语句实现7.16和7.17的功能。

7.22、在程序模块中，伪指令PUBLIC和EXTRN的作用是什么？

7.23、在C语言程序中，存在关键字EXTERN，它与汇编语言中EXTRN的作用相似吗？

7.24、如何创建和维护自己的子程序库？使用子程序库有何好处？


第8章 输入输出和中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

第8章 输入输出和中断

输入输出功能是计算机的重要组成部分，是人—机交互功能的主要承担者。在早期的计算机系统中，通常把输入输出设备或功能作为次要的部分，而把CPU作为主要研究对象。但现在随着输入输出设备的日益丰富、功能要求越来越复杂，输入输出部分在整个计算机系统中的地位也得到了进一步提高。

本章先介绍了I/O的基本概念和I/O指令，再叙述了中断的概念及其工作过程，并列举出计算机系统中若干个常用的中断及其功能。

8.1 输入输出的基本概念

输入输出是一个完整应用程序的重要组成部分，是交互式应用程序不可缺少的组成部分。

在用高级语言编程时，程序员可直接用输入输出语句来完成键盘输入、屏幕显示或打印输出等需求，而无需关心这些输入输出语句是如何实现的，因为编译程序会自动把这些语句转换成相应的输入输出指令。但如果用汇编语言编写程序的话，情况就不同了，因为汇编语言是与机器有关的程序设计语言，要编写出具有输入输出功能的代码段就必须清楚CPU为输入输出提供了哪些指令，或计算机系统提供了哪些可直接使用的功能调用。

8.1.1 I/O端口地址

I/O端口是CPU与输入输出设备的交换数据的场所，通过I/O端口，处理机可以接受从输入设备输入的信息；也可向输出设备发送信息。在计算机系统中，为了区分各类不同的I/O端口，就用不同的数字给它们进行编号，这种对I/O端口的编号就称为I/O端口地址。按照

每次可交换一个字节数据的端口称为字节端口，每次可交换一个字数据的端口称为字端口。

在Intel公司的CPU家族中，I/O端口的地址空间可达64K，即可有65536个字节端口，或32768个字端口。这些地址不是内存单元地址的一部分，不能普通的访问内存指令来读取其信息，而要用专门的I/O指令才能访问它们。虽然CPU提供了很大的I/O地址空间，但目前大多数微机所用的端口地址都在0~3FFH范围之内，其所用的I/O地址空间只占整个I/O地址空间的很小部分。表8.1列举了几个重要的I/O端口地址。

表8.1 几个重要的I/O端口地址

端口地址 | 端口名称 | 端口地址 | 端口名称
020H~023H | 中断屏蔽寄存器 | 378H~37FH | 并行口LPT2
040H~043H | 时针/计数器 | 3B0H~3BBH | 单色显示器端口
060H | 键盘输入端口 | 3BCH~3BFH | 并行口LPT1
061H | 扬声器(0, 1位) | 3C0H~3CFH | VGA/EGA
200H~20FH | 游戏控制口 | 3D0H~3DFH | CGA
278H~27FH | 并行口LPT3 | 3F0H~3F7H | 磁盘控制器
2F8H~2FFH | 串行口COM2 | 3F8H~3FFH | 串行口COM1

计算机在启动时，BIOS程序(Basic Input/Output System)将检查计算机系统中有哪些端口地址。当发现有串行端口地址时，BIOS就把 该端口存放在以地址40:00H开始的数据区内；当发现有并行端口地址时，BIOS会把它 存入以地址40:08H开始的数据区内。

每类端口有4个字的空间，对有二个串行口、二个并行口的计算机系统，其BIOS程序将得到如图8.1所示的部分数据表。

图中03F8H、02F8H、0378H和0278H分别为COM1、COM2、LPT1和LPT2的端口地址。

图8.1 BIOS部分数据表


8.1.2 I/O指令

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ I/O的基本概念 → I/O指令

8.1.2 I/O指令

由于I/O端口地址和内存单元地址是相互独立的，这些端口地址不能普通的访问内存指令来访问其信息，所以，在CPU的指令系统中就专门设置了I/O指令来存取I/O端口的信息。按功能分类来看，I/O指令应属于数据传送指令。

1、输入指令IN

输入指令IN的一般格式如下：

IN AL/AX, PortNo/DX

该指令的作用是从端口中读入一个字节或字，并保存在寄存器AL或AX中。如果某输入设备的端口地址在0~255范围之内，那么，可在指令IN中直接给出，否则，要把该端口地址先存入寄存器DX中，然后在指令中由DX来给出其端口地址。

例如：
 | IN | AL, 60H | ;从端口60H读入一个字节到AL中
 | IN | AX, 20H | ;把端口20H、21H按“高高低低”组成的字读入AX
 | MOV | DX, 2F8H
 | IN | AL, DX | ;从端口2F8H读入一个字节到AL中
 | IN | AX, DX | ;把端口2F8H、2F9H按“高高低低”组成的字读入AX

2、输出指令OUT

输出指令OUT的一般格式如下：

OUT PortNo/DX, AL/AX

该指令的作用是把寄存器AL或AX的内容输出到指定端口。如果某输出设备的端口地址在0~255范围之内，那么，可在指令OUT中直接给出，否则，要把该端口地址先存入寄存器DX中，然后在指令中由DX来给出其端口地址。

例如：
 | OUT | 61H, AL | ;把AL的内容输出到端口61H中
 | OUT | 20H, AX | ;把AX的内容输出到端口20H、21H中
 | MOV | DX, 3C0H
 | OUT | DX, AL | ;把AL的内容输出到端口3C0H中
 | OUT | DX, AX | ;把AX的内容输出到端口3C0H、3C1H中

有关字符串的输入输出指令，请见5.2.11节中的介绍。


8.2 中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断

8.2 中断

在计算机系统中，引入中断的最初目的是为了提高系统的输入输出性能。随着计算机应用的发展，中断技术也应用到计算机系统的许多领域，如：多道程序、分时系统、实时处理、程序监视和跟踪等领域。

8.2.1 中断的基本概念

下面只简单介绍与汇编语言程序设计有关的中断知识，使本章的知识具有一定完整性。有关中断的详细介绍可参阅《计算机组成原理》课程中的相关章节。

1、中断和中断源

所谓中断就是CPU暂停当前程序的执行，转而执行处理紧急事务的程序，并在该事务处理完后能自动恢复执行原先程序的过程。在此，称引起紧急事务的事件为中断源，称处理紧急事务的程序为中断服务程序或中断处理程序。 计算机系统还根据紧急事务的紧急程度，把中断分为不同的优先级，并规定：高优先级的中断能暂停低优先级的中断服务程序的执行。

计算机系统有上百种可以发出中断请求的中断源，但最常见的中断源是：外设的输入输出请求，如：键盘输入引起的中断，通信端口接受信息引起的中断等；还有一些计算机内部的异常事件，如：0作除数、奇偶校验错等。

CPU在执行程序时，是否响应中断要取决于以下三个条件能否同时满足：

(1)、有中断请求；
(2)、允许CPU接受中断请求；
(3)、一条指令执行完，下一条指令还没有开始执行。

条件(1)是响应中断的主体。除用指令INT所引起的软件中断之外，其它中断请求信号是随机产生的，程序员是无法预见的。

程序员可用程序部分地控制条件(2)是否满足，即可用指令STI和CLI来允许或不允许CPU响应可屏蔽的外部中断。而对于不可屏蔽中断和内部中断，CPU一定会响应它们的，程序员是无控制权的。CPU一定会执行这些中断的中断服务程序。

2、断向量表和中断服务程序

中断向量表是一个特殊的线性表，它保存着系统所有中断服务程序的入口地址(偏移量和段地址)。在微机系统中，该向量表有256个元素(0~0FFH)，每个元素占4个字节，总共1K字节，其在内存中的存储形式及其存储内容如图8.2所示。

图8.2中的“中断偏移量”和“中断段地址”是指该中断服务程序入口单元的“偏移量”和“段地址”。由此不难看出：假如中断号为n，那么，在中断向量表中存储该中断处理程序的入口地址的单元地址为：4n。

表8.2说明了前16个中断向量表中列举了部分常用的中断号。

图8.2 中断向量表

表8.2 部分常用的中断号及其含义

中断号 | 含义 | 中断号 | 含义
0 | 除法出错 | 8 | 定时器
1 | 单步 | 9 | 键盘
2 | 非屏蔽中断 | A | 未用
3 | 断点 | B | COM2
4 | 溢出 | C | COM1
5 | 打印屏幕 | D | 硬盘(并行口)
6 | 未用 | E | 软盘
7 | 未用 | F | 打印机


8.2.2 中断指令

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断 → 中断指令

8.2.2 引起中断的指令

中断处理程序基本上是系统程序员编写好的，是为操作系统或用户程序服务的。为了在应用程序中使用中断服务程序，程序员必须能够在程序中有目的地安排中断的发生。为此，指令系统提供了各种引起中断的指令。

1、中断指令INT

中断指令INT的一般格式如下：

INT Imm

其中：立即数Imm是一个0~0FFH范围内的整数。

指令执行的步骤：

◆、把标志寄存器压栈，清除标志位IF和TF；
◆、把代码段寄存器CS的内容压栈，并把中断服务程序入口地址的高字部分送CS；

◆、32位段，压32位IP。

在该指令执行完后，CPU将转去执行中断服务程序。由于有了指令INT，程序员就能为满足某种特殊的需要，在程序中有目的地安排中断的发生，也就是说，该中断不是随机产生的，而是完全受程序控制的。

一般情况下，一个中断可有很多不同的功能，每个功能都有一个唯一的功能号，所以，在安排中断之前， 程序员还要决定需要该中断的哪个功能，中断的功能号都是由AH来确定的。有些中断还需要其它参数，常用中断的功能和参数如附录3所列。

2、溢出指令INTO

当标志位OF为1时，引起中断。该指令的格式如下：

INTO

该指令影响标志位：IF和TF。


8.2.3 中断返回指令

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断 → 中断返回指令

8.2.3 中断返回指令

当一个中断服务程序执行完毕时，CPU将恢复被中断的现场，返回到引起中断的程序中。为了实现此项功能，指令系统提供了一条专用的中断返回指令。该指令的格式如下：

IRET/IRETD

该指令执行的过程基本上是INT指令的逆过程，具体如下：

◆、从栈顶弹出内容送入IP；
◆、再从新栈顶弹出内容送入CS；
◆、再从新栈顶弹出内容送入标志寄存器；

对80386及其以后的CPU，指令IRETD从栈顶弹出32位内容送入EIP。

8.2.4 中断和子程序的比较

中断和子程序调用之间有其相似和不同之处。它们的工作过程非常相似，即：暂停当前程序的执行，转而执行另一程序段，当该程序段执行完时，CPU都自动恢复原程序的执行。

如图8.3所示。

图8.3 中断和子程序调用的工作过程

它们的主要差异有：
◆、 | 子程序调用一定是程序员在编写源程序时事先安排好的，是可知的，而中断是由中断源根据自身的需要产生的，是不可预见的(用指令INT引起的中断除外)；
◆、 | 子程序调用是用CALL指令来实现的，但没有调用中断的指令，只有发出中断请求的事件(指令INT是发出内部中断信号，而不要理解为调用中断服务程序)；
◆、 | 子程序的返回指令是RET，而中断服务程序的返回指令是IRET/IRETD。
◆、 | 在通常情况下，子程序是由应用系统的开发者编写的，而中断服务程序是由系统软件设计者编写的。


8.3 中断功能的分类

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类

8.3. 中断功能的分类

计算机系统有上百种中断，若按中断的性质来划分，则系统中的中断可分为：可屏蔽中断和不可屏蔽中断。对不可屏蔽中断，程序员不能控制它，系统肯定会立即响应的，而对于可屏蔽中断，汇编语言程序员可以通过指令CLI和STI来控制对它们的响应。

若按中断源来划分，则系统中的中断又可分为：硬件中断和软件中断。对于硬件中断，程序员不能控制它，它们基本上是随机产生的，而对于软件中断，汇编语言程序员可通过指令INT和INTO来有目的安排它们的。

下面主要介绍汇编语言程序员能控制的软件中断的功能及其使用方法，常用的这类中断有：DOS功能调用(INT 21H)、BIOS中断、硬件和外设的中断等。

图8.4给出了程序员可使用的各类中断之间的层次关系。

图8.4 各类系统中断之间的层次关系

在用户程序中，若直接通过端口来操作硬件或外设，那么，其处理过程没有额外的多余工作，处理速度显然是最快的，但这样做，无疑使用户程序具有了很大的局限性。硬件环境的改变将直接影响程序的正常运行。

若用户程序通过调用DOS功能来实现其所需功能，那么，应用程序与低层硬件相距较远，操作最终的对象需要经过中间环节，处理速度肯定受到一定的损失，但这种应用程序适应性强，应用范围广，对硬件的依赖性最小。

由于BIOS介于DOS和具体硬件之间，所以，调用BIOS的功能是一个很好的折中方案。程序员可在以下三种情况下考虑使用BIOS的功能：

1)、BIOS提供的功能，而DOS没有提供该功能的情况；
2)、不能利用DOS功能调用的情况(可能因为某些具体应用的限制)；
3)、基于处理速度的考虑，需要绕过DOS层的情况。

综上所述，可以归纳出如下结论：使用中断的层次越高，它与硬件设备相关程度就越低，处理速度也就越低，但用户程序的适用范围较广。反之也然。

有了上面的结论，程序员可根据应用程序的要求、对硬件环境的熟悉程度等因素来选用不同层次的中断。


8.3.2 键盘输入中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 键盘的中断功能

8.3.1 键盘输入的中断功能

键盘输入是绝大多数程序的主要输入方式，学习和掌握有关键盘输入中断的使用方法对编写交互式程序是非常重要的，也能更进一步理解计算机是如何接受键盘输入的。

1、键盘缓冲区

键盘缓冲区是一个先进先出的环形队列，其所占内存区域如下：

KBHead | DW ? | ;其内存地址为0000:041AH，缓冲区头指针
KBTail | DW ? | ;其内存地址为0000:041CH，缓冲区尾指针
KBBuff | DW 16 DUP(?) | ;其内存地址为0000:041EH，该缓冲区的缺省长度为16个字

与键盘有关的其它地址请见附录6之键盘地址。

键盘缓冲区是一个环形队列，其性质与《数据结构》课程中对“环形队列”所描述的性质完全一致。虽然缓冲区的本身长度为16个字，但出于判断“对列满”的考虑，它最多只能保存15个键盘信息。当缓冲区满时，系统将不再接受按键信息，而会发出“嘟”的声音，以示要暂缓按键。当KBHead＝KBTail时，表示无键盘输入。

2、键盘状态字

在计算机键盘上除了可输入各种字符(字母、数字和符号等)的按键之外，还有一些功能键(如：F1、F2、…等)、控制键(如：Ctrl、Alt、Shift等)、双态键(如：Num Lock、Caps Lock等)和特殊请求键(如：Print Screen、Scroll Lock等)。

键盘中的控制键和双态键是非打印按键，它们是起控制或转换作用的。当使用者按下控制键或双态键时，系统要记住其所按下的按键。为此，在计算机系统中，特意安排的一个字来标志这些按键的状态，我们称该字为键盘状态字。

键盘状态字的各位含义如图8.5所示。

图8.5 键盘状态字的各位含义


8.3.2 键盘中断的处理过程

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 键盘中断的处理过程

3、键盘中断的处理过程

当用户按键时，键盘接口会得到一个代表该按键的键盘扫描码，同时产生一个中断请求。键盘中断服务程序先从键盘接口取得按键的扫描码，然后根据其扫描码判断用户所按的键并作相应的处理，最后通知中断控制器本次中断结束并实现中断返回。

若用户按下双态键(如：Caps Lock、Num Lock和Scroll Lock等)，则在键盘上相应LED指示灯的状态将发生改变；
若用户按下控制键(如：Ctrl、Alt和Shift等)，则在键盘标志字中设置其标志位；
若用户按下功能键(如：F1、F2、…等)，再根据当前是否又按下控制键来确定其系统扫描码，并把其系统扫描码和一个值为0的字节存入键盘缓冲区；
若用户按下字符键(如：A、1、+、…等)，此时，再根据当前是否又按下控制键来确定其系统扫描码，并得到该按键所对应的ASCII码，然后把其系统扫描码和ASCII码一起存入键盘缓冲区；
若用户按下功能请求键(如：Print Screen等)，则系统直接产生一个具体的动作。

有关键盘中各种单键、组合键的扫描码及其相应的ASCII码，请参阅本书的附录5。

4、DOS功能中的键盘输入

键盘输入是一种最常用的输入方式，所以，在DOS操作系统中，提供了能实现各种键盘输入的功能(Windows操作系统中仍然能用)。INT 21H中的相关功能如下：

01H——带回显的键盘输入 | 06H——控制台的输入/输出：当DL=0FFH，表示键盘输入
07H——不回显、不过滤的键盘输入 | 08H——不回显的键盘输入
0AH——键盘输入字符串 | 0BH——检查键盘输入状态
0CH——清除输入缓冲区的输入功能

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之字符功能调用类。

5、BIOS中的键盘输入

在BIOS系统中，提供了中断16H来实现键盘输入功能。其具体的功能如下：

00H、10H——从键盘读一个字符，输入字符不回显
01H、11H——判断键盘缓冲区内是否有字符可读
02H——读取当前键盘状态字

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之键盘服务。

6、直接操作端口的键盘输入

由表8.1可知：键盘输入端口的地址为60H，所以，我们可以用指令IN从该端口读取当前按键的扫描码。

例如：

MOV　DX, 60H
IN　　 AL, DX


8.3.2 键盘中断举例

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 键盘的中断举例

7、键盘输入的举例

例8.1 用键盘最多输入10个字符，并存入内存变量Buff中，若按“Enter”键，则表示输入结束。

解：

1、方法1

.MODEL SMALL
CR EQU 0DH |  | ;定义“回车”键的符号名
.DATA
Buff DB 10 DUP(?)
.CODE
 | .STARTUP
 | MOV CX, 0AH
 | LEA BX, Buff
 | .REPEAT
 | MOV AH, 0H
 | INT 16H | ;用BIOS中的中断功能
 | .BREAK .IF AL==CR
 | MOV [BX], AL
 | INC BX
 | .UNTILCXZ
 | .EXIT 0
 | END

2、方法2

.MODEL SMALL
.DATA
Buff DB 10, ?, 10 DUP(?) | ;注意缓冲区的定义方式
.CODE
.STARTUP
LEA DX, Buff
MOV AH, 0AH
INT 21H | ;用DOS中的功能调用
.EXIT 0
END


8.3.2 屏幕中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 屏幕显示的中断功能

8.3.2 屏幕显示的中断功能

显示器是一个重要的输出设备，它通过显示卡与计算机系统相连。显示器的显示屏通常称之为屏幕，现在常用的显示器有14"和17"，常用的显示分辨率为800×600或1024×768等。常用的显示卡类型为VGA、SVGA、EVGA和TVGA等，显示卡上也都带有大量的显示存储器，能快速显示精美的图象。

1、显示模式

计算机系统中的显示器都有二种显示方式：文本显示方式和图形显示方式。在DOS操作系统环境下，其默认的显示方式为文本显示方式，而在Windows操作系统环境下，其显示方式是图形显示方式，其绝大多数操作界面是以图形界面的窗口形式出现的。

可用的显示模式，请参阅附录3中的中断10H之功能00H中所列。

1)、文本显示方式

文本显示方式是指以字符为最小单位的显示方式，每个字符都是以矩形块形式显示的。在BIOS ROM中存有多种不同大小的字符集，主要的显示字符集大小为：8×8(标准)、8×14和8×16等。

在常用的文本显示模式(模式3)下，屏幕被划分成25行，每行可显示80个字符，所以，每屏最多可显示2000(80×25)个字符。为了便于标识屏幕上的每个显示位置，我们就用其所在行和列来表示之，并规定：屏幕的左上角坐标为(0, 0)，右下角坐标为(24, 79)。

在显示字符时，用一个字节存储该字符的ASCII码，用另一个字节存储的显示属性，即：显示颜色。彩色显示器的字符显示属性定义如图8.6所示，有关颜色值的定义，请参阅附录6中的表4。

图8.6 字符显示属性的定义

存储内容与显示位置的对应关系如图8.7所示。

图8.7 存储内容与显示位置的对应关系示意图

由上面的叙述可知：在80×25的文本显示模式下，满屏可显示2000个字符，也就需要4000个字节来存储一屏的显示信息。


8.3.2 屏幕中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 屏幕显示的中断功能

2)、图形显示方式

图形显示是目前最常用的一种显示方式，也是Windows操作系统的默认显示方式。在该显示方式下，我们可以看到优美的图象、VCD、浏览丰富多彩的网页等。

图形显示的最小单位是象素，对每个象素可用不同的颜色来显示。所以，在显示缓冲区内记录的信息是屏幕各象素的显示颜色。

由于各种图形显示模式所能显示的颜色和象素是不同的，它决定了显示缓冲区的存储方式也是不同的。下面给出三个具体的图形显示模式及其存储方式，通过它们可看出各种显示模式在显示缓冲区存储方式上的明显差异。

(1)、4色320×200图形显示模式

由于每个象素只能是四种颜色之一，而四种情况用2位二进制就可表示，所以，一个字节可表示4个象素的显示颜色，存储一行上的所有象素信息就需要80个字节。

在具体存储过程中，它又把偶数行象素和齐数行分开来存储。偶数行和齐数行的象素总数各有32000个，也都需要8000个字节来存储，并规定：偶数行象素从0B800:0000H开始存储，齐数行象素从0B800:2000H开始存储。该显示模式的存储形式如图8.8所示。

图8.8 显示缓冲区存储方式示意图

(2)、16色640×480图形显示模式

640×480图形显示模式共有307200个象素，每个象素可选用16种颜色，它需要用4位二进制来表示。该显示模式在存储显示信息时，把该4位分在四个位平面P1、P2、P3和P4上，所以，位平面Pi(i=1,2,3,4)共有307200个二进制位，即有38400个字节。其显示缓冲区的存储形式如图8.9所示。

图8.9 显示缓冲区存储方式示意图

由图8.9可看出：若要改变某个象素的显示颜色，则需要在每个位平面上修改其对应的二进制位。所以，若用操作显示缓冲区的方法来改变象素的颜色，那么编程将困难得多。在屏幕上显示一个点，必须遵循以下步骤：

读入要改变的内存单元，把位平面信息装入显示卡；
通过图形地址寄存器(GAR)和位屏蔽寄存器(BMR)选择并寻址到其二进制位；
寻址并设定映像屏蔽寄存器(MMR)为0FH，在对应象素地址填入0(黑色)来清除象素的原有颜色；
通过MMR设定该象素当前所要颜色；
通过修改包含显示信息的内存单元来写象素。

对于该显示模式下，改变象素方法的详细描述和例子，请参阅参考书籍[2]、[4]，或其它有关技术资料，在此不再进一步展开叙述。

若用BIOS中断10H之功能0DH/0CH来读/写象素，则它可屏蔽掉各种显示模式的差异，用同样的参数完成同样的功能。所以，在此建议：使用系统中断的方法来实现对图形象素的操作。

(3)、256色320×200图形显示模式

表达256种不同颜色需要8位二进制，即一个字节。在该模式下，其显示缓冲区的存储方式是非常简单的，即：第一个字节存储第一个象素的颜色，第二个字节存储第二个象素的颜色，以此类推，所以，存储满屏象素所需要的字节数为：320×200×1＝64000。其显示缓冲区的存储方式如图8.10所示。

从上面三种不同图形显示模式的介绍，不难看出：各种显示模式在显示缓冲区存储方式上的明显差异，操作象素方法的难易程度相差也很大，所以，再次建议：程序员不要用直接操作显示缓冲区的办法来达到改变显示象素的目的，最好是通过BIOS内的中断功能来实现相应的功能，这样，所编写的程序能很方便地适应不同的图形显示模式。

图8.10 显示缓冲区存储方式示意图


8.3.2 屏幕中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 屏幕显示的中断功能

2、显示缓冲区

显示缓冲区是用来记录屏幕显示信息的。在文本显示方式下，这些显示信息包括：每个显示字符的ASCII码及其显示属性，如图8.7所示。在图形显示方式下，显示缓冲区内存储每个象素的显示颜色。

在图8.7中，我们并没有给出具体的段地址，只用"XXXX"来表示其段地址。在常用的VGA显示方式下，计算机系统规定：

文本显示方式下，单色显示器的显示缓冲区段地址为0B000H；
文本显示方式下，彩色显示器显示缓冲区段地址为0B800H；
图形显示方式下，其显示缓冲区段地址为0A000H。

3、DOS功能中的屏幕输出

屏幕输出是最常用的一种输出形式，DOS操作系统提供了几种实现屏幕输出的功能调用。INT 21H中的相关功能如下：

02H——显示的字符
06H——控制台的输入/输出：当DL≠0FFH，表示显示字符
09H——在屏幕上显示一个字符串

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之字符功能调用类。

4、BIOS中的屏幕操作功能

BIOS系统提供了中断10H来实现各种屏幕处理功能。其具体的功能如下：

00H——设置显示器模式 | 01H——设置光标形状
02H——设置光标位置 | 03H——读取光标信息
05H——设置显示页 | 06H、07H——初始化或滚屏(向上滚屏和向下滚屏)
08H——读光标处的字符及其属性 | 09H——在当前光标处按指定属性显示字符
0AH——在当前光标处显示字符 | 0CH——写图形象素
0DH——读图形象素 | 0EH——在Teletype模式下显示字符
0FH——读取显示器模式 | 10H——颜色
13H——在Teletype模式下显示字符串

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之显示服务。

5、屏幕输出的举例

例8.2 用直接写屏方式在屏幕第5行、第10列以黄色(0EH)显示字符串"Hello"。

解：在文本显示方式下，每行显示80个字符，每个字符占2个字节，所以，显示一行需要160个字节。若在第m行、第n列位置显示字符，则该位置所对应存储单元的偏移量为：m×160 + n×2。

.MODEL SMALL .DATA Yellow EQU 0EH MSG1 DB "Welcome..." Count EQU $-MSG1 .CODE .STARTUP
MOV | AX, 0B800H
MOV | ES, AX | ;彩色显示器的显示缓冲区段地址
MOV | DI, 5
IMUL | DI, 160 | ;5行所跳过的显示存储单元
MOV | CX, 10
SHL | CX, 1 | ;10列所跳过的显示存储单元
ADD | DI, CX | ;第5行、第10列之前所跳过的所有显示单元
MOV | CX, Count
LEA | SI, MSG1 | ;CX:字符个数，SI:显示字符首地址
MOV | AH, Yellow | ;设置显示属性，即显示颜色
.REPEAT
LODSB |  | ;取显示字符
STOSW |  | ;向显示缓冲区设置显示字符和属性
.UNTILCXZ
.EXIT 0
END

例8.3 用“霓虹灯”的显示方式显示字符串"Hello"，按ESC键时结束程序的运行。

解：显示解答

例8.4 编写一个输入密码的程序，该程序的具体要求如下：

1、每输入一个字符，显示字符"#"表示之；
2、密码最多只有10个字符，多余的按键被丢弃；
3、若输入的字符串为"HELLO"，则以蓝色显示"Welcome…"，否则，以闪烁、亮红色在显示"Invalid Password"。

解：显示解答

例8.5 在256色320×200的图形显示模式下，从屏幕最左边向最右边，依次画竖线(从顶到底)，线的颜色从1依次加1。要求用中断调用的方法来画线。

解：显示解答

例8.6 在256色320×200的图形显示模式下，从屏幕顶到屏幕底依次画横线(从最左边到最右边)，线的颜色从1依次加1。要求用直接操作显示缓冲区的方法来画线。

解：显示解答


8.3.3 屏幕显示中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 打印输出的中断功能

8.3.3 打印输出的中断功能

打印输出是一种硬拷贝输出，也是一种常用的输出形式。随着计算机应用领域的不断扩大，外围设备的重视程度也越来越高。目前，打印机的种类已日益丰富，有针式打印机、喷墨打印机和激光打印机等。从打印色彩来看，既有普通的黑色打印，也有彩色打印。总之，计算机打印输出的品质是越来越高。

1、打印机状态字

打印机状态字记录着打印机的当前工作情况(或状态)，它相当于CPU中的程序状态字和键盘状态字。打印机状态字的各位定义如图8.11所示。

若在某个程序中要包含打印功能，那么，程序员可通过打印状态字来了控制打印过程，并向使用者提供各种有用信息。比如：提示使用者“打印机无纸了”或打印机处于脱机状态(Off Line)等。

图8.11 打印机状态字节定义

2、打印控制命令

在打印时，程序员往往要控制文本的输出格式，比如：换页、换行、字体或字号等。若要实现对打印格式的控制，就必须要知道控制打印机的控制字符和控制命令。一些常用的打印控制字符如表8.3所示。

表8.3 常用的打印控制字符

字符值 | 功能描述
09H | 水平制表符，跳到下一个制表位置
0AH | 换行
0CH | 换页
0DH | 回车

打印机还有其它的控制命令，如：ESC命令序列，该序列由字符ESC(其ASCII为1BH)和一些数值组成。表8.4列举了LQ-1600K打印机的几个控制命令。对于其它各类打印机的控制命令，要参阅其使用手册。

表8.4 LQ-1600K打印机的几个控制命令

控制命令 | 命令的数值 | 功能描述
ESC @ | 1BH 40H | 初始化打印机
ESC 4/ESC 5 | 1BH 34H／1BH 35H | 设定／取消斜体打印
ESC S 0/ESC S 1 | 1BH 53 0／1BH 53 1 | 设定上／下角标打印
ESC T | 1BH 54H | 取消上／下角标打印
ESC l n | 1BH 6CH n | 设定左边界，n为当前字符的宽度

3、DOS功能中的打印输出

在DOS操作系统中，INT 21H提供了一种打印输出的功能调用。其具体描述如下：

05H——向连接在LPT1端口上的打印机输出一个字符
40H——向先前打开的文件写入指定数量的字节，可以把打印机看作为标准的输出文件

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之字符功能调用类。

4、BIOS中的打印输出

BIOS系统提供了中断17H来实现打印输出功能。其具体的功能如下：

00H——向指定的打印机输出一个字符
01H——初始化指定的打印机
02H——读取指定打印机的状态

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之并行口服务。

5、打印输出的举例

例8.7 在每页的开始处打印“Assember Language”字符串，并空一行才打印其它内容。

解：显示解答

例8.8 当打印机不能正常打印(非硬件故障)时，提示使用者其原因，以便解决打印问题。

解：显示解答


8.3.4 串行口中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 串行通信口的中断功能

8.3.4 串行通信口的中断功能

计算机的通信功能是现代计算机网络的最基本功能。如果计算机没有通信功能，计算机网络环境也就无从谈起。一台计算机与其它计算机(或设备)之间通信除了必要的物理连接之外，它们之间通信接口的功能就是另一个极其重要的功能模块。

从计算机通信的本质来看，通信就是发送或接受具有一定格式的二进制位。这些二进制位通过一些应用程序——如浏览器——的解释显示成具有不同表达形式的文字或图象。

为了方便实现计算机的通信功能，在系统的低层或操作系统中都有不同程度的支持，如：DOS、BIOS和NETBIOS等系统中都相应通信功能或中断服务。下面仅仅介绍在汇编语言程序中如何使用通信功能，有关通信功能的详细内容在《计算机通信与接口技术》课程会有更深入的论述。

1、DOS中的通信功能

INT 21H提供了对通信口COM1操作的功能调用。其具体描述如下：

03H——从辅助设备读入一个字符，该辅助设备的缺省值为COM1
04H——向辅助设备输出一个字符，该辅助设备的缺省值为COM1

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之字符功能调用类。

2、BIOS中的通信功能

BIOS系统提供了中断14H来实现对通信端口的控制能。这些具体的功能如下：

00H——初始化通信口
01H——向通信口输出字符 		02H——从通信口读入字符
03H——读取通信口状态

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之串行口服务。

3、通信功能的应用举例

例8.9 把字符串"Hello, World"从COM1端口传输出去。在传输过程中，要求传输速率为9600波特，字长为8位，1位停止位，无齐偶校验。

解：
 | .MODEL SMALL .DATA MSG DB "Hello, World" MLen EQU $-MSG .CODE
 |  | .STARTUP
 |  | MOV | AL, 0E3H | ;0E3—9600、无齐偶校验、字长8位，1位停止位
 |  | MOV | DX, 0
 |  | MOV | AH, 0
 |  | INT | 14H | ;初始化通信口COM1
 |  | MOV | BX, OFFSET MSG
 |  | MOV | CX, MLen
 |  | MOV | DX, 0
 | again:
 |  | MOV | AL, [BX]
 |  | MOV | AH, 1
 |  | INT | 14H
 |  | TEST | AH, 80H | ;如果发送字符失败，继续发同一个字符，这里可能
 |  | JNZ | again | ;构成死循环。在实际工作过程中，还有其它考虑。
 |  | INC | BX
 |  | LOOP | again
 |  | .EXIT | 0
 |  | END |

例8.10 把从COM1端口读入的字符显示在屏幕上。如果通信端口数据未准备好，则程序处于等待状态；若传输出错，则用红色显示字符"?"。

解：
 |  | ……
 | again:
 |  | MOV | DX, 0
 |  | MOV | AH, 3
 |  | INT | 14H
 |  | TEST | AH, 1H | ;检测状态字节AH的最后一位，看数据是否准备好
 |  | JZ | again
 |  | MOV | DX, 0
 |  | MOV | AH, 2
 |  | INT | 14H | ;从COM1端口读字符
 |  | TEST | AH, 80H | ;检测读字符是否成功
 |  | .IF ZERO? |  | ;读字符成功
 |  | AND AL, 7FH |  | ;屏蔽掉最高位，使之为字符
 |  | MOV BL, 15 |  | ;正常颜色：白色
 |  | .ELSE
 |  | MOV AL, '?' |  | ;显示字符"?"
 |  | MOV BL, 12 |  | ;错误颜色：红字
 |  | .ENDIF
 |  | MOV | BH, 0
 |  | MOV | AH, 0EH
 |  | INT | 10H
 |  | JMP | again
 |  | ……


8.3.5 鼠标功能中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 鼠标的中断功能

8.3.5 鼠标的中断功能

鼠标是现在计算机系统中的一个常用输入设备，它为使用计算机带来了很大的方便，鼠标指针的各种表现形式还反映了系统(或应用程序)当前处于什么样的工作状态。许多计算机使用者为体现其个性化还选择各自喜欢的鼠标图形，所以，了解和掌握计算机系统对鼠标所提供的功能对理解鼠标的各种操作也是有一定的帮助。

1、鼠标中断的常用功能

BIOS系统提供了中断33H来实现鼠标中断功能，其常用的功能如下：

00H—初始化鼠标
01H—显示鼠标指针
02H—隐藏鼠标指针
03H—读取鼠标位置及其按钮状态
04H—设置鼠标指针位置
05H—读取鼠标按键信息
06H—读取鼠标按钮释放信息
07H—设置鼠标水平边界
08H—设置鼠标垂直边界
09H—设置图形鼠标形状 		0AH—设置本文鼠标形状
0BH—读取鼠标移动计数
0CH—为鼠标事件设置处理程序
0FH—设置鼠标计数与象素比
10H—设置鼠标指针隐藏区域
14H—替换鼠标事件中断
18H—为鼠标事件设置替换处理程序
19H—读取替换处理程序的地址
1DH—为鼠标指针选择显示页
1EH—读取鼠标指针的显示页

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之鼠标功能中断。

2、鼠标指针的设置

鼠标指针主要有二种表现形式：文本鼠标和图形鼠标。文本鼠标又分为软指针和硬指针。软指针是用各种字符来作鼠标指针，而硬指针是用方块光标的大小来表示鼠标指针，其效果类似于INT 10H之功能01H——设置光标类型。

图形鼠标的表现形式与文本鼠标相比就显得更加丰富多彩，可用各种不同的指针形状来反映系统当前所处的工作状态和所能进行的操作。在Windows操作系统及其应用程序中使用了很多指针形状的变化来表达各种有用信息。

鼠标的文本软指针与图形指针的形成过程相一致，它需要两部分信息：象素掩码和光标掩码。其指针形成过程如下：

1)、用象素掩码与当前鼠标所处位置的象素信息进行“逻辑与”运算；
2)、光标掩码与步骤1的运算结果再进行“异或”操作，该操作所得到的16×16位的0/1信息就构成了当前鼠标指针的形状。

假设象素掩码为全0。全0的象素掩码与屏幕上的显示信息“逻辑与”后，所得结果仍为全0，全0的运算结果再和“光标掩码”进行“异或”操作，这时，所得结果显然与“光标掩码”完全一样，所以，看到的鼠标指针形状就是光标掩码所表达的指针形状。

综合上述，可得结论：若象素掩码为全0，那么，鼠标的形状就是16×16位光标掩码所表示的指针形状，鼠标所到之处就看不到该区域内(16×16点阵范围)的其它显示信息。

在Windows操作系统及其应用程序中，在16×16点阵范围内，除了看不见被各种形状指针覆盖的部分之外，还能看见其它小区域，这是因为鼠标的“象素掩码”取其“光标掩码”的反相点阵所致。感兴趣的读者可验证或证明之。

(1)、硬指针的设置

设置鼠标硬指针的中断功能使用方式：

MOV | BX, 1 | ;硬指针
MOV | CX, 01H | ;鼠标硬指针的起始扫描线
MOV | DX, 0FH | ;鼠标硬指针的结束扫描线
MOV | AX, 0AH | ;设置文本鼠标指针
INT | 33H

(2)、软指针的设置

设置鼠标软指针的中断功能使用方式：

MOV | BX, 0 | ;软指针
MOV | DL, 'A' | ;用字符'A'作为鼠标指针符号
MOV | DH, 07FH | ;置鼠标的颜色
MOV | CX, 0 | ;置象素位掩码
MOV | AX, 0AH
INT | 33H | ;设置文本鼠标指针

(3)、图形指针的设置

设置鼠标软指针的中断功能使用方式：

……
PMask | DW 16 dup(?) | ;象素位掩码，PMask可以是~CMask
CMask | DW 16 dup(?) | ;光标掩码，二者紧相邻
……
MOV | AX, DS
MOV | ES, AX
LEA | DX, PMask | ;ES:DX＝象素位掩码的起始地址
MOV | BX, 0
MOV | CX, 0 | ;在鼠标指针范围内，(0,0)点为指示点
MOV | AX, 09H
INT | 33H | ;设置图形鼠标指针

3、鼠标功能的应用举例

例8.11 在屏幕的右上角动态显示文本鼠标的位置，即：鼠标的任何移动都将马上显示其所处位置，按鼠标左键结束程序的运行。

解：显示解答

例8.12 编写可随时修改文本鼠标指针符号的程序，即要求：在程序运行过程中，随时在键盘上按什么字符，即以该字符为鼠标指针符号。

解：显示解答

读者可把例8.11和8.12结合在一起，使之：一方面，可动态显示鼠标的位置，另一方面，也可随时修改文本鼠标的指针符号。

改变图形鼠标指针是许多计算机使用者体现其个性的方法之一，读者可参考例8.12和前面的“图形指针的设置”部分的叙述，编写一个显示自己设计的鼠标指针的程序。在学习了有关文件操作知识之后，还可利用现有的鼠标指针文件来显示鼠标指针。


8.3.6 通信口输入输出中断

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 操作目录和文件的中断功能

8.3.6 目录和文件的中断功能

中断21H提供了许多有关目录和文件操作的功能，其中常用的功能如下：

1、操作目录的常用功能

39H——创建目录 | 3BH——设置当前目录
3AH——删除目录 | 47H——读取当前目录

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之目录控制功能。

2、用文件句柄操作文件的常用功能

3CH——创建文件 | 4EH——查找到第一个文件
3DH——打开文件 | 4FH——查找下一个文件
3EH——关闭文件 | 56H——文件换名
3FH——读文件或设备 | 57H——读取/设置文件的日期和时间
40H——写文件或设备 | 5AH——创建临时文件
41H——删除文件 | 5BH——创建新文件
42H——设置文件指针 | 67H——设置文件句柄数(最多文件数)
43H——读取/设置文件属性 | 6CH——扩展的打开文件功能

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之文件操作功能。

3、用FCB操作文件的常用功能

0FH——打开文件 | 21H——随机读
10H——关闭文件 | 22H——随机写
13H——删除文件 | 23H——读取文件的大小
14H——顺序读 | 24H——设置相对记录数
15H——顺序写 | 27H——随机读块
16H——创建文件 | 28H——随机写块
17H——文件换名

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之文件操作功能(FCB)和记录操作功能(FCB)。

4、磁盘绝对读写中断

中断25H——磁盘绝对读中断 中断26H——磁盘绝对写中断

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之其它DOS中断。

5、系统标准设备的句柄

0000H——键盘 | 0001H——屏幕
0002H——错误显示(屏幕) | 0003H——COM1
0004H——打印机

6、目录、文件功能的应用举例

例8.13 编写一个创建子目录的程序，具体要求如下：
1)、用键盘输入一个目录路径名，若输入的字符串为空，则程序运行结束；
2)、若目录创建成功，显示成功信息，否则，显示创建失败信息。

解：显示解答

例8.14 编写一个类似TYPE命令的程序，其要求如下：
1)、用键盘输入文件名(可包含路径)，若输入的字符串为空，则程序运行结束；
2)、若输入的文件存在，则显示其内容，否则，显示文件不存在的信息。

解：显示解答


8.3.7 内存管理的中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 内存管理的中断功能

8.3.7 内存管理的中断功能

在C/C++语言环境中，若事先不知道数据的容量，那么，可通过动态申请空间的方法来解决数据的存储问题，这种动态数据结构可用中断21H所提供的存储管理功能来实现。

中断21H在内存管理方面所提供的主要功能如下：

48H——分配内存块
49H——释放内存块
4AH——重定义内存块的大小
58H——读取/设置内存分配策略，其最基本的分配策略有三种：第一满足、最好满足和最后满足

有关中断功能的详细描述和调用参数在此从略，需要查阅者可参阅附录3之内存分配功能。

有关存储分配策略的含义可参阅《操作系统》课程中“内存管理”部分的介绍。

8.3.8 读取和设置中断向量

中断向量是系统用来存放中断服务程序或系统参数的入口地址。在通常情况下，程序员不需要对中断向量表作任何操作，但在开发程序时，若要为某种特殊需要而提供新的中断处理程序时，则其就必须要操作中断向量表。

1、读取中断向量

(1)、DOS功能调用的方法

DOS提供了用系统调用的方法来读取中断向量，其中断21H之功能35H就能读取指定中断号的入口地址。其使用参数如下：

入口参数：AH＝35H，AL＝中断号
出口参数：ES:BX＝中断处理程序的入口地址

例8.15 用功能调用的方法把中断n的入口地址保存到双字变量OldAddr中。

解：
 | …… OldAddr DD ? ……
 | MOV | AL, n | ;这里的n要用具体的中断号来定
 | MOV | AH, 35H
 | INT | 21H
 | MOV | word ptr OldAddr, BX
 | MOV | word ptr OldAddr+2, ES | ;把其入口地址保存在存储单元中
 | ……

(2)、直接访问存储单元的方法

由前面的图8.2不难看出：中断向量表是存储在内存的第0段。若中断号为n，那么，其入口地址在表内的偏移量为4n。

例8.16 用直接访问存储单元的方法把中断n的入口地址保存到双字变量OldAddr中。

解：
 | …… OldAddr DD ? ……
 | MOV | AX, 0H
 | MOV | ES, AX | ;中断向量表存储在第0段内
 | MOV | BX, 4*n
 | MOV | AX, ES:[BX] | ;读取中断入口地址的偏移量
 | MOV | word ptr OldAddr, AX | ;保存中断入口地址的偏移量
 | MOV | AX, ES:[BX+2] | ;读取中断入口地址的段地址
 | MOV | word ptr OldAddr+2, ES | ;保存中断入口地址的段地址
 | ……


8.3.8 设置中断向量

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 设置中断向量

2、设置中断向量

(1)、DOS功能调用的方法

中断21H之功能25H可为指定的中断号设置新的入口地址。其使用方法如下：

入口参数：AH＝中断号，DS:DX＝中断处理程序的入口地址
出口参数：无

例8.17 用功能调用的方法把子程序NewFunc设置为中断n的中断处理程序。

解：
 | ……
 | NewFunc | PROC
 |  | ……
 |  | IRET |  | ;注意其返回指令
 | NewFunc | ENDP
 |  | ……
 |  | MOV | AX, SEG NewFunc
 |  | MOV | DS, AX | ;设置段地址寄存器
 |  | MOV | DX, OFFSET NewFunc | ;设置偏移量
 |  | MOV | AL, n | ;这里的n要用具体的中断号来定
 |  | MOV | AH, 25H
 |  | INT | 21H
 |  | ……

(2)、直接访问存储单元的方法

例8.18 用直接访问存储单元的方法把子程序NewFunc设置为中断n的中断处理程序。

解：
 | ……
 | NewFunc | PROC
 | NewFunc | ……
 |  | IRET |  | ;注意其返回指令
 |  | ENDP
 |  | ……
 |  | MOV | AX, 0H
 |  | MOV | DS, AX
 |  | MOV | BX, 4*n
 |  | CLI
 |  | MOV | word ptr [BX], OFFSET NewFunc | ;设置中断处理程序的偏移量
 |  | MOV | word ptr [BX+2], SEG NewFunc | ;设置中断处理程序的段地址
 |  | STI
 |  | ……

在上面程序段中，指令CLI是一条值得注意的指令，它用来确保随后二条MOV指令被连续执行而不被打断。这是因为执行其第一条MOV指令后，原中断向量表中的入口地址就被破坏了，这时，该入口地址既不是指向原处理程序，也不指向新处理程序。如果此刻正巧发生了该类型的中断请求，那么，系统将转向一个非法的位置。如果程序员能确信此时不会发生该类型的中断请求，当然也就不必插入关中断指令。

后面的指令STI是开中断指令，它允许CPU响应其后的中断请求。


8.3.7 内存管理的中断功能

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 中断功能的分类 → 修改中断向量

3、修改中断向量的步骤

前面，分别介绍了读取和设置中断入口地址的方法。在编程的实际过程中，若要修改某个具体的中断处理程序时，一般需按下面几步来完成：

1)、读取指定中断的中断处理程序入口地址，并把它保存在存储单元内；
2)、把用户编写的程序段设置为指定中断的新处理程序；
3)、在用户程序结束之前(或不需要新的处理程序时)，把步骤1保存的入口地址恢复成处理该中断的入口地址。

程序员按以上三步来编程，可保证：在其程序运行过程中，该指定的中断将按新的处理程序来处理，程序结束后，中断系统又恢复成原来的处理方式。所以，这种中断向量的改变对其它程序或使用者来说是透明的，当然也就不会影响它们的正常运作。

以上三步具体化的形式如下：

……
INTNO | EQU 40H |  | ;假设被修改的中断号
OldAddr | DD ?
……
NewFunc | PROC |  | ;新的中断处理程序
NewFunc | ……
 | IRET
 | ENDP
 | ……
 | MOV | AL, INTNO
 | MOV | AH, 35H
 | INT | 21H
 | MOV | word ptr OldAddr, BX
 | MOV | word ptr OldAddr+2, ES | ;步骤1：保存原入口地址
 | MOV | DX, OFFSET NewFunc
 | MOV | AX, SEG NewFunc
 | MOV | DS, AX
 | MOV | AL, INTNO
 | MOV | AH, 25H
 | INT | 21H | ;步骤2：设置新的入口地址
 | …… ;用户编写的主要程序 ……
 | MOV | DX, word ptr OldAddr
 | MOV | DS, word ptr OldAddr+2 | ;本指令与上一条指令能交换吗？
 | MOV | AL, INTNO
 | MOV | AH, 25H
 | INT | 21H | ;步骤3：恢复原入口地址
 | …… END


8.4 习题

1 I/O端口地址
1 I/O端口地址
2 I/O指令
2 I/O指令
1 中断的基本概念
1 中断的基本概念
2 引起中断的指令
2 引起中断的指令
3 中断返回指令
3 中断返回指令
4 中断和子程序的比较
4 中断和子程序的比较
1 键盘输入的中断功能
1 键盘输入的中断功能
2 屏幕显示的中断功能
2 屏幕显示的中断功能
3 打印输出的中断功能
3 打印输出的中断功能
4 串行通信口的中断功能
4 串行通信口的中断功能
5 鼠标的中断功能
5 鼠标的中断功能
6 目录和文件的中断功能
6 目录和文件的中断功能
7 内存管理的中断功能
7 内存管理的中断功能
8 读取和设置中断向量
8 读取和设置中断向量
本章的学习内容和要求
本章的学习内容和要求
8.1 输入输出的基本概念
8.1 输入输出的基本概念
8.2 中断
8.2 中断
8.3 中断功能的分类
8.3 中断功能的分类
8.4 习题
8.4 习题

→ 习题

8.4 习题

8.1、简述中断和子程序调用之间的主要区别？

8.2、为什么要区分IRET指令与RET指令?

8.3、编写一个子程序，它可显示以0结尾的字符串。子程序的入口参数DS:DX为待输出字符串的首地址。

8.4、编写一个子程序，用来读入一个键，并在屏幕上按十六进制的形式显示按键的扩展ASCII码，如果按键为普通字符，则不显示。

8.5、编写一个程序，在屏幕的右下角闪烁显示编程者自己的姓名，显示颜色自定。

8.6、编写一个把屏幕上显示的字母经过大小写转换后再显示的程序。

8.7、编写一个程序，它把屏幕上的数字改位蓝绿色背景的红字。

8.8、编写一个控制光标位置和形状的程序，该程序具有以下功能：

1)、可用光标移动键↑、↓、←和→来移动光标；

2)、当光标已在第0列，且按'←'键时，光标定在上一行的最后一列；若已在屏幕的左上角，则光标不动，且给出响铃；按'→'键时的边界处理类似；

3)、当光标在第0行，且按'↑'键时，则光标不动，且给出响铃；按'↓'键时的边界处理类似；

4)、按Home或End键，则光标移到当前行的行首或行尾；

5)、若按下数字或字母键，则把该字符从当前位置依次显示到屏幕顶(在新位置显示字符时，原位置的符号被抹去)；

6)、按Esc键，程序结束。

8.9、交换例8.5和8.6的画线方法，编写相应的子程序VLine和HLine，并验证之。

8.10、假设显示器的显示模式设定为12H，编写实现下列功能的程序：

1)、在屏幕中间从上到下显示一条明亮的蓝色线，线宽为1个像素；
2)、在屏幕底下横向画一条绿色线，线宽为2个像素；
3)、在屏幕上垂直显示16种颜色，每种颜色宽40个像素；
4)、设定屏幕背景为白色，在屏幕中间画一条青色线，线宽为10个像素。

8.11、对于256色，320X200的显示模式需要多少个字节的内存存放一屏信息?

8.12、为什么在显示数据时必须关闭鼠标?

8.13、编写程序，检测计算机是否已安装了鼠标，并以显示Yes/No来表示检测结果。

8.14、编写程序，显示鼠标的按键，若按左键，显示"Left"，若按右键，显示"Right"，按Esc键，程序结束。

8.15、编写一个程序，在图形模式下，打开鼠标，并显示鼠标的位置。

8.16、编写一个程序，显示驱动器C根目录下所有EXE文件。提示：用通配符"*.exe"来查找与此匹配的文件名，找到后，显示之。

8.17、编写一个程序，显示驱动器C根目录下的所有子目录。

8.18、编写一个程序，设置指定目录下的所有文件为只读文件。

8.19、编写一个程序，创建一个隐含文件Data.TXT。

8.20、编写一个程序，把键盘上输入写入文件File.dat中，类似以下DOS命令：

COPY　con　File.dat

8.21、使用哪个DOS功能可读取中断向量地址，哪个DOS功能可把新的中断服务程序地址设置为指定中断的入口地址。

8.22、编写一个程序，在程序开始运行时，把中断向量表保存到程序的数据段中，在程序结束前把数据段中的备份恢复到系统中去。

8.23、把下列C语言的子程序改写成汇编语言子程序。

1)、取当前光标的位置，并分别存入变量x和y中

get_cursor(int *x, int *y)
{union REGS in, out;
in.h.ah = 3;　　　in.h.bh = 0;
int86(0x10, &in, &out);
*x = out.h.dh;　 *y = out.h.dl;
}

2)、把光标设置在(row, col)位置

locate(int row, int col)
{union REGS in, out;
in.h.ah = 2;　　 in.h.bh = 0;
in.h.dh = row;　 in.h.dl = col;
int86(0x10, &in, &out);
}

3)、在(row, col)位置显示字符串string

disp_string(int row, int col, char string[])
{struct　REGPACK in, out;
int　　 x, y;
get_cursor(&x, &y);
locate(row, col);
in.r_ds = FP_SEG(string);
in.r_dx = FP_OFF(string);
in.r_ax = 0x900;
intr(0x21, &in);
locate(x, y);
}


第9章 宏

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

第9章 宏

宏是程序设计的另一个基本概念，它是把一段程序代码用一个特定标识符(即：宏名)来表示。这样，在编写源程序时，程序员就可以直接使用该标识符来代替一段代码的编写，从而减少了重复代码的编写工作，也为减少错误，提高程序的可维护性提供了帮助。宏在高级语言(如：C/C++等)也有广泛的使用。

9.1 宏的定义和引用

通常情况下，宏是用来代表一个具有特定功能的程序段，它只需在源程序中定义一次，但可在源程序中引用多次。只要在编写程序时需要，就可以直接使用它。

9.1.1 宏的定义

在使用宏之前必须先定义宏，定义宏一般格式如下：

宏名 | MACRO [形参1, 形参2, ……]
 | … | ;宏的定义体
 | ENDM

在书写宏定义时，必须遵照下列规定：

、MACRO和ENDM是二个必须成对出现的关键字，它们分别表示宏定义的开始和结束；

、MACRO和ENDM之间的部分是宏的定义体，它是由指令、伪指令或引用其它宏所组成的程序片段，是宏所包含的具体内容；

、“宏名”是由程序员指定的一个合法的标识符，它代表该宏；
、宏名可以与指令助忆符、伪指令名相同。在这种情况下，宏指令优先，而同名的指令或伪指令都失效；
、在ENDM的前面不要再写一次宏名，这与段或子程序定义的结束方式有所不同；
、在宏定义的首部可以列举若干个形式参数，每个参数之间要用逗号分隔。

根据上述规定，我们提倡宏名尽可能不要与指令助忆符、伪指令相名，以免引起不必要的误会。

例9.1 定义一个把16位数据寄存器压栈的宏。

PUSHR | MACRO
 | PUSH | AX
 | PUSH | BX
 | PUSH | CX
 | PUSH | DX
 | ENDM

例9.2 定义二个字存储变量相加的宏。

MADDM | MACRO OPRD1, OPRD2
 | MOV | AX, OPRD2
 | ADD | OPRD1, AX
 | ENDM

上述宏定义虽然能满足题目的要求，但由于在定义体中改变了寄存器AX的值，这就使宏的引用产生了一定的副作用。为了使寄存器AX的使用变得透明，可把该宏定义改成如下形式：

MADDM | MACRO OPRD1, OPRD2
 | PUSH | AX
 | MOV | AX, OPRD2
 | ADD | OPRD1, AX
 | POP | AX
 | ENDM

通过在宏定义的开始和结尾分别增加对所用寄存器的保护和恢复指令，就使得：对该宏的任意引用都不会产生任何副作用。


9.1.2 宏的引用

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的定义和引用 → 宏的引用

9.1.2 宏的引用

在源程序中，一旦定义了某宏，那么，在该程序的任何位置都可直接引用该宏，而不必重复编写相应的程序段。引用宏的一般格式如下：

宏名　[实参1, 实参2, ……]

其中：实参的位置要与形参的位置要对应，但实参的个数可以与形参的个数不相等。

、当实参的个数多于形参的个数时，多出的实参被忽略；

、当实参的个数少于形参的个数时，没有实参对应的形参用“空”来对应。但在宏展开时，所得到的指令必须是合法的汇编指令，否则，汇编程序将会给出出错信息。

例如：假设已有字变量w1和w2，并且也有例9.2中的宏MADDM，那么，如果要把w2的内容加到w1中的话，就可以在源程序的代码段中按下列方式来引用该宏(点击它，可显示宏展开后的形式)：

        MADDM w1, w2
    1　　　MOV　AX, w2
    1　　　ADD　w1, AX

注意：以上的宏展开形式可以在LST文件中看到，宏展开所得到的语句前面有一个数字(如：1)，它表示宏展开的层次。

如果在源程序中按下列方式来引用了宏MADDM，那么，在宏展开时就会得到下列语句：

        MADDM w1
    1　　　MOV　AX,　　　　;显然，指令“MOV AX”是一条非法指令，汇编程序将会指出该错误。
    1　　　ADD　w1, AX

9.1.3 宏的参数传递方式

在引用宏时，参数是通过“实参”替换“形参”的方式来实现传递的。参数的形式灵活多样，参数可以是常数、寄存器、存储单元和表达式，还可以是指令的操作码。

例9.3 定义二个字存储变量相加和相减的宏。

解：

方法1：定义二个宏分别实现存储变量的加操作和减操作

 方法2：定义一个宏，把存储变量的加和减操作合并在一起
MADDM | MACRO OPRD1, OPRD2
 | MOV AX, OPRD2
 | ADD OPRD1, AX
 | ENDM
MSUBM | MACRO OPRD1, OPRD2
 | MOV AX, OPRD2
 | SUB OPRD1, AX
 | ENDM
MOPM MACRO OP, OPRD1, OPRD2
MOV　AX, OPRD2
OP　　OPRD1, AX
ENDM

其中：参数OP是一个对应于操作码的形式参数。

下面是引用宏MOPM的二个例子，它们在宏展开时将会得到下列语句：

        MOPM SUB, w1, w2
    1　　　MOV　AX, w2
    1　　　SUB　w1, AX
        …
        MOPM ADD, w1, w2
    1　　　MOV　AX, w2
    1　　　ADD　w1, AX


9.1.4 宏的嵌套定义

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的定义和引用 → 宏的嵌套定义

9.1.4 宏的嵌套定义

宏的嵌套定义有二种方式：宏定义体内引用其它的宏和宏定义体内定义其它的宏。

1、宏定义体内引用其它的宏

在宏的定义体中又引用了其它已定义好的宏，这种宏定义方式在实际的编程过程时常会用到。如果被引用的宏还没定义的话，汇编程序将会显示出错信息。

例如：
 | ABS | MACRO OPRD1, OPRD2
 |  | …
 |  | MOPM SUB, OPRD1, OPRD2 | ;引用前面已定义的宏MOPM
 |  | …
 |  | ENDM

在定义宏ABS时，引用了前面已定义好的宏MOPM。

2、宏定义体内定义其它的宏

宏的定义体内又定义了其它宏，只有在先引用了外层的宏定义，才能引用内层的宏，这是因为，当外层宏展开后，内层宏的定义才变得有效。

这种定义方式虽然有其好的一面，但它使宏的定义更加隐蔽，削弱了程序的可读性，降低了程序的可维护性，因此，这种宏定义方式在实际的编程中很少使用。

例9.4 假设有三个内存字变量Oprd1，Oprd2和Oprd3，编写宏定义实现下面功能(其中：OP是除加、减之外的二元操作)：

Oprd1 ← Oprd2 OP Oprd3

解：
方法1：用宏嵌套定义的方式来实现
 | OPMM | MACRO | NAME, OP
 |  | NAME | MACRO | OPRD1, OPRD2, OPRD3
 |  |  | PUSH | AX
 |  |  | MOV | AX, OPRD2
 |  |  | OP | AX, OPRD3
 |  |  | MOV | OPRD1, AX
 |  |  | POP | AX
 |  |  | ENDM
 |  | ENDM

这时，如果要完成下列操作(其中：w1，w2和w3是内存字变量)：

w1 ← w2 + w3 w1 ← w2 OR w3

那么，可按下列宏引用的方式来完成所需要的功能：

OPMM MADDM, ADD 1 MADDM MACRO OPN1, OPN2, OPN3 1 PUSH AX 1 MOV AX, OPN2 1 ADD AX, OPN3 1 MOV OPN1, AX 1 POP AX 1 ENDM | ;宏引用语句(1)：在宏扩展后将得到一个新的宏定义MADDM——把后二个内存字变量之和赋给第一个内存字变量：
OPMM MORM, OR 1 MORM MACRO OPN1, OPN2, OPN3 1 PUSH AX 1 MOV AX, OPN2 1 OR AX, OPN3 1 MOV OPN1, AX 1 POP AX 1 ENDM | ;宏引用语句(2)：在宏扩展后将得到另一个新的宏定义MORM——把后二个内存字变量之“逻辑或”赋给第一个内存字变量：
MADDM w1, w2, w3 1 PUSH AX 1 MOV AX, W2 1 OR AX, W3 1 MOV W1, AX 1 POP AX 1 ENDM | ;有了宏引用语句(1)和(2)所得到的宏定义，当前宏引用语句和下一条才有效，并在宏扩展时能得到相应的程序片段。
MORM w1, w2, w3 1 PUSH AX 1 MOV AX, W2 1 OR AX, W3 1 MOV W1, AX 1 POP AX 1 ENDM

方法2：把所要运算的指令操作符作为参数进行传递

OPMM | MACRO | OP, OPRD1, OPRD2, OPRD3
 | PUSH | AX
 | MOV | AX, OPRD2
 | OP | AX, OPRD3
 | MOV | OPRD1, AX
 | POP | AX
 | ENDM

有了上述定义之后，可按下列宏引用的方式来完成上面的“加”和“逻辑或”的功能，单击它们可显示宏扩展时所得到的程序片段：

        OPMM ADD, w1, w2, w3
    1　　　　　PUSH　AX
    1　　　　　MOV　 AX, w2
    1　　　　　ADD　 AX, w3
    1　　　　　MOV　 w1, AX
    1　　　　　POP　 AX
        OPMM OR, w1, w2, w3
    1　　　　　PUSH　AX
    1　　　　　MOV　 AX, w2
    1　　　　　OR　　AX, w3
    1　　　　　MOV　 w1, AX
    1　　　　　POP　 AX

从上面二种方法的比较来看，方法2显然要简单、直观些，程序的可读性和可维护性也要高一些。所以，通常情况下，宏定义的嵌套方式在实际编程过程中是很少使用的。


9.1.5 宏与子程序的区别

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的定义和引用 → 宏与子程序的区别

9.1.5 宏与子程序的区别

宏和子程序都是为了简化源程序的编写，提高程序的可维护性，但是它们二者之间存在着以下本质的区别：

1、在源程序中，通过书写宏名来引用宏，而子程序是通过CALL指令来调用；

2、汇编程序对宏通过宏扩展来加入其定义体，宏引用多少次，就相应扩展多少次，所以，引用宏不会缩短目标程序；而子程序代码在目标程序中只出现一次，调用子程序是执行同一程序段，因此，目标程序也得到相应的简化；

3、宏引用时，参数是通过“实参”替换“形参”的方式来实现传递的，参数形式灵活多样，而子程序调用时，参数是通过寄存器、堆栈或约定存储单元进行传递的；

4、宏引用语句扩展后，目标程序中就不再有宏引用语句，运行时，不会有额外的时间开销，而子程序的调用在目标程序中仍存在，子程序的调用和返回均需要时间。

总之，当程序片段不长，速度是关键因素时，可采用宏来简化源程序，但当程序片段较长，存储空间是关键因素时，可采用子程序的方法来简化源程序和目标程序。


9.2 宏参数的特殊运算符

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏参数的特殊运算符

9.2 宏参数的特殊运算符

为了宏定义和引用的某些特殊需要，汇编程序还支持几个具体特定含义的运算符。

9.2.1 连接运算符

在宏定义中，如果形式参数与其它字符连接在一起，或形式参数出现在字符串之中，那么，就必须使用连接运算符(&)。

例9.5 定义一个转移宏JUMP，其一个参数决定转移类别，另一个参数指定转移目标。

解：
 | JUMP | MACRO | CON, here
 |  | J&CON | here
 |  | ENDM

假设存在下面二个引用语句，那么，点击它们可得到扩展后的指令。

        JUMP mp, next
    1　　　　 Jmp　next

…

        JUMP nz, next1
    1　　　　 Jnz　next1

例9.6 定义一个问候性的字符串宏GREETING，其一个参数说明字符串的变量名，另一个参数指名问候的对象。

解：
 | GREETING | MACRO | MSG, name
 |  | MSG | DB 'Hello, &name'
 |  | ENDM

假设有下面引用语句，那么，点击它们将会扩展得到三个问候性的字符串定义。

        GREETING STR1, 张三
    1 　　　　STR1　DB　'Hello, 张三'

…

        GREETING STR2, 李四
    1　　　　 STR2　DB　'Hello, 李四'

…

        GREETING MSG1, John
    1　　　　 MSG1　DB　'Hello, John'

9.2.2 字符串整体传递运算符

字符串整体传递运算符是一对尖括号<>，用它括起来的内容将作为一个字符串来进行形式参数的整体替换。在宏引用时，如果实参内包含逗号、空格等间隔符，则必须使用该操作符，以保证实参的完整性。如果实参是某个具有特殊含义的字符，为了使它只表示该字符本身，也需要用该运算符括起来。

假设有下面定义字符串的宏DEFMSG，
 | DEFMSG | MACRO | MSG
 |  | DB | '&MSG', 0DH, 0AH, '$'
 |  | ENDM

那么，使用和不使用该运算符的引用宏及其宏扩展如下所示：

        DEFMSG <Are you ready?>
    1　　　　 DB 'Are you ready?', 0DH, 0AH, '$'

…

        DEFMSG Are you ready?
    1　　　　 DB 'Are', 0DH, 0AH, '$'


9.2.3 字符转义运算符

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏参数的特殊运算符 → 字符转义运算符

9.2.3 字符转义运算符

在引用宏时，如果实参中含有特殊字符，而又要该特殊字符当作普通字符来出来，那么，就必须在该特殊字符前加上字符转义运算符“!”。

下面不使用和使用字符转义运算符的宏引用语句及其宏扩展的结果：

        DEFMSG <Input one number(>90):>
    1　　　　 DB 'Input one number(90):', 0DH, 0AH, '$'
              ;第一个“>”与字符“<”相比配，而不会把它当作“大于号”字符来处理

…

        DEFMSG <Input one number(!>90):>
    1　　　　 DB 'Input one number(>90):', 0DH, 0AH, '$'
    ;由于在第一个“>”字符前面有字符转义运算符“!”，所以，汇编程序会把第一个“>”当作“大于号”字符来处理，而把最后面的字符“>”当作是与前面“<”相比配的结束符。

9.2.4 计算表达式运算符

在引用宏时，使用计算表达式运算符“%”表示把其后面表达式的结果当作实参进行替换，而不是该表达式的整个式子。

下面使用和不使用计算表达式运算符的宏引用语句及其宏扩展的结果：

        DEFMSG %200+23-100
    1　　　　DB '123', 0DH, 0AH, '$'　　;先计算出表达式200+23-100的值，然后再把该值作为参数进行替换

…

        DEFMSG 200+23-100
    1　　　　DB '200+23-100', 0DH, 0AH, '$'　 ;把整个表达式200+23-100当作一个字符串来进行参数替换


9.3 与宏有关的伪指令

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 与宏有关的伪指令

9.3 与宏有关的伪指令

在宏定义时，为了满足某种特殊需要，汇编语言还提供了几个伪指令。

9.3.1 局部标号伪指令LOCAL

在宏定义体中，如果存在标号，则该标号要用伪指令LOCAL说明为局部标号，否则，当在源程序中，有多于一次引用该宏时，汇编程序在进行宏扩展后将会给出：标号重复定义的错误。

伪指令LOCAL的一般格式如下：

LOCAL　标号1, 标号2, ……

伪指令LOCAL必须是伪指令MACRO后的第一条语句，并且在MACRO和LOCAL之间也不允许有注释和分号标志。

汇编程序在每次进行宏扩展时，总是把由LOCAL说明的标号用一个唯一的符号(从??0000到??FFFF)来代替，从而避免标号重定义的错误。

例9.7 编写求一个求绝对值的宏。

解：
 | 方法1：
 | ABS | MACRO | word1
 |  | CMP | word1, 0
 |  | JGE | next
 |  | NEG | word1
 | next:
 |  | ENDM

假设对宏ABS有以下两次引用，点击它们将会显示汇编程序对它们进行宏扩展后所得到程序片段：

        ABS BX
    1　　　　 CMP　BX, 0
    1　　　　 JGE　 next
    1　　　　 NEG　BX
    1　next:

…

        ABS AL
    1　　　　 CMP　AL, 0
    1　　　　 JGE　 next
    1　　　　 NEG　AL
    1　next:

在上述程序片段中，显然标号next定义了二次，所以，汇编程序将显示“标号重复定义”的错误信息。为了避免这种情况的发生，我们需要用下面的方法来定义该宏。

方法2：
 | ABS | MACRO | word1
 |  | LOCAL | next
 |  | CMP | word1, 0
 |  | JGE | next
 |  | NEG | word1
 | next:
 |  | ENDM

假设对宏ABS有以下两次引用，点击它们将会显示汇编程序对它们进行宏扩展时所得到程序片段：

        ABS BX
    1　　　　　　CMP　BX, 0
    1　　　　　　JGE　 ??0000
    1　　　　　　NEG　BX
    1　??0000:

…

        ABS AL
    1　　　　　　CMP　AL, 0
    1　　　　　　JGE　??0001
    1　　　　　　NEG　AL
    1　??0001:

在上述程序片段中，宏体内部的局部标号next分别用符号??0000和??0001来对应它的二次引用。因此，汇编程序不会再显示“标号重复定义”的错误信息。

伪指令LOCAL在子程序中也可起作用(参见7.5.10节)，但它的作用与宏定义的作用是不同的，有关该伪指令在子程序的宏定义中功能的主要差异如表9.1所列。

表9.1 伪指令LOCAL在子程序和宏中的比较

 | 在子程序中 | 在宏定义中
语句的位置 | 在所有指令之前 | 在所有指令之前
伪指令的作用 | 说明局部变量 | 说明局部标号
伪指令的格式 | 可用一条伪指令来说明多个局部变量，也可连续用多条伪指令来说明 | 可用一条伪指令来说明多个局部标号，也可连续用多条伪指令来说明
调用或引用 | 子程序的不同调用，其局部变量名保存不变 | 在每次宏引用的扩展时，将会自动产生出一个唯一的局部标号


9.3 与宏有关的伪指令

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 与宏有关的伪指令 → 与宏有关的伪指令

9.3.2 取消宏定义伪指令

伪指令PURGE的一般格式如下：

PURGE　宏名1, 宏名2, ……

该伪指令通知汇编程序取消“宏名1, 宏名2, ……”宏名表中的宏定义。在此语句后，如果还有这些宏的引用语句，则汇编程序不会把它们当作宏引用来进行扩展，并且还将显示出错信息。

伪指令PURGE的使用频率较低。

9.3.3 中止宏扩展伪指令

伪指令EXITM的一般格式如下：

EXITM

该伪指令书写在宏定义体中，用来告诉汇编程序：如果遇到该伪指令，那么，立即中止对该伪指令之下语句的扩展。如果在嵌套的内层宏中遇到了该伪指令，则退出到宏嵌套的外层。

在一般情况下，伪指令EXITM与条件伪指令一起使用，以便在不同的条件下挑选出不同的语句。

伪指令EXITM的使用频率也很低。


9.4 重复汇编伪指令

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 重复汇编伪指令

9.4 重复汇编伪指令

在编写源程序时，有时会出现连续相同或相似的语句(组)。当出现这种情况时，可利用重复伪指令来重复语句，从而达到简化程序的目的。

重复汇编伪指令所定义的重复块是宏的一种特殊形式，也是由伪指令ENDM来结束重复块。用重复汇编伪指令定义的重复块也可带有参数，并在汇编过程中参数被实参代替，但重复块不会被命名，不能在程序的其它地方引用。

9.4.1 伪指令REPT

伪指令REPT的作用是把一组语句重复指定的次数，该重复次数由伪指令后面的数值表达式来确定。其一般使用格式如下：

REPT 数值表达式
重复的语句组
ENDM

例9.8 定义100个初值为32的字节单元，该存储单元的起始符号地址为Table。

解：
方法1：用伪指令REPT来实现 |  |  | 左边重复块的汇编结果如下：
 | Table LABEL TYPE |  | Table LABEL TYPE
 | REPT | 100 | DB 32
 |  | DB 32 | …
 |  | ENDM | DB 32 ;上述字节定义重复100次

方法2：用重复操作符DUP来实现

Table　DB　100 DUP(32)

从上例来看，用伪指令REPT重复定义的存储单元可以用重复操作符DUP来代替，其实前者的功能会更灵活、更强大。

例9.9 定义100个初值分别为1,2,…,100的字节单元，该存储单元的起始符号地址为Table。

解：
 | Table LABEL TYPE |  | 左边重复块的汇编结果相当于：
 |  | COUNT = 1 | Table LABEL TYPE
 | REPT | 100 |  | DB 1
 |  | DB COUNT |  | DB 2
 |  | COUNT = COUNT + 1 |  | …
 |  | ENDM |  | DB 100

上面定义了100个字节，其初值为1,2,…,100。本例好象不能用重复操作符DUP来说明字节存储单元。

例9.10 计算1+2+…+1000，并把其值存入寄存器AX。

解：
方法1：用伪指令REPT来实现 |  |  | 左边重复块的汇编结果与下面程序段相一致：
 | … |  | …
 | MOV AX, 0 |  | MOV AX, 0
 | COUNT = 1 |  | ADD AX, 1
 | REPT | 1000 | ADD AX, 2
 |  | ADD AX, COUNT | …
 |  | COUNT = COUNT + 1 | ADD AX, 1000 ;把AX从1累加到1000
 |  | ENDM | …
 | … |  | 虽然上面这些语句的执行能完成本例所指定的功能，但它是用1000条加法指令来直接计算的，这1000条指令无疑会大大增加目标代码的长度。
方法2：用循环指令LOOP来实现
 | …
 | MOV | AX, 0
 | MOV | CX, 1000
again: | ADD | AX, CX
 | LOOP | again
 | …

由例9.10，不难看出：伪指令REPT与循环指令起作用的时期和方式是截然不同的。它们之间的主要差异如表9.1所列。

表9.1 伪指令REPT与循环指令LOOP之间的主要差异

 | 伪指令REPT | 循环指令LOOP
起作用的时期 | 汇编程序把源文件翻译成目标文件时期 | 程序的执行时期
起作用的方式 | 把被重复的指令(组)直接重复写入目标文件 | 通过反复执行同一指令(组)来实现重复
重复次数对目标文件的影响 | 由于重复次数决定着被重复指令(组)写入目标文件的次数，所以，改变重复次数一定会改变目标文件的字节数 | 由于重复的指令数与重复次数无关，所以，改变重复次数不会改变目标文件的字节数


9.4 重复汇编伪指令

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 重复汇编伪指令 → 伪指令IRP

9.4.2 伪指令IRP

伪指令IRP的作用是用每个参数创建一组语句，其重复次数由伪指令后面参数表中参数的个数来确定。其一般使用格式如下：

IRP 形式参数, <实参1, 实参2, ……, 实参n>
重复的语句组
ENDM

例9.11 把16位通用寄存器之值相加，并把结果存入寄存器AX。

解：由于16位通用寄存器名是一些不同的符号，不能用计数的方法来依次访问它们，所以，我们需要用伪指令IRP来实现。

IRP REG, <BX, CX, DX, SP, BP, SI, DI>
ADD AX, REG
ENDM

9.4.3 伪指令IRPC

伪指令IRPC的作用与IRP相似，其实参表是一个字符串，并对字符串中的每个字符创建一组语句，所以，其重复次数是由该字符串中的字符数来确定。其一般使用格式如下：

IRPC 形式参数, 字符串
重复的语句组
ENDM

例9.12 定义10个字节存储单元，保存数字0~9的平方数。

解：

IRPC X, 0123456789
DB X*X
ENDM

例9.13 把16位数据寄存器之值相加，并把结果存入寄存器DI。

解：由于16位数据寄存器是AX、BX、CX和DX，它们的名称中只有第一个字符不同，所以，可以用伪指令IRPC来实现。

XOR | DI, DI
IRPC | REG, ABCD
 | ADD DI, REG&X ;符号&是连接运算符
 | ENDM


9.5 条件汇编伪指令

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 条件汇编伪指令

9.5 条件汇编伪指令

条件汇编伪指令是告诉汇编程序：根据某种条件确定一组程序段是否加入到目标程序中。使用条件汇编伪指令的主要目的是：同一个源程序能根据不同的汇编条件生成不同功能的目标程序，增强宏定义的使用范围。

条件汇编伪指令与高级语言(如：C/C++)的条件编译语句在书写形式上相似，在所起作用方面是完全一致的。

9.5.1 条件汇编伪指令的功能

条件汇编伪指令的一般格式如下：

IFnnnn | 条件表达式
 | 语句组1
[ELSE
 | 语句组2]
ENDIF

其中：IFnnnn是表9.2中的伪指令，“[…]”内的语句是可选的。

条件汇编伪指令是在汇编程序把源程序转换成目标程序时起作用，其一般含义是：若条件汇编伪指令后面的“条件表达式”为真，那么，语句组1将被汇编；否则，语句组2将被汇编(如果含有ELSE伪指令)。

语句组1或语句组2内还可以包有条件汇编伪指令，这时，就形成了嵌套的条件汇编伪指令。一个嵌套的ELSE伪指令总是与最近的、还没有与其它ELSE伪指令相比配的IFnnnn伪指令相比配。

每条条件汇编伪指令的具体含义如表9.3所示。

表9.3 条件汇编伪指令及其功能一览表

伪指令 | 含义
IF exp | 若数值表达式exp的值不为0，则语句组1包含在目标文件中
IFE exp | 若数值表达式exp的值为0，则语句组1包含在目标文件中
IFDEF label | 若标号label有定义或被说明为EXTRN，则语句组1包含在目标文件中
IFNDEF label | 若标号label没有定义，也没被说明为EXTRN，则语句组1包含在目标文件中
IFB <参数> | 在宏引用时，若该形参没有相应的实参相对应，则语句组1包含在目标文件中
IFNB <参数> | 在宏引用时，若该形参没有相应的实参相对应，则语句组1包含在目标文件中
IFIDN <参数1>, <参数2> | 若参数1＝参数2，则语句组1包含在目标文件中
IFDIF <参数1>, <参数2> | 若参数1≠参数2，则语句组1包含在目标文件中
IF1 | 若汇编程序在第一遍扫描时，则语句组1包含在目标文件中
IF2 | 若汇编程序在第二遍扫描时，则语句组1包含在目标文件中

9.5.2 条件汇编伪指令的举例

例9.14 编写一个可用DOS或BIOS功能调用输入字符的宏定义。

解：
 | 方法1：使用条件汇编伪指令IF
 | INPUT | MACRO
 |  | IF | DOS | ;当符号DOS不为0时，则使用DOS的功能调用
 |  |  | MOV AH, 1H
 |  |  | INT 21H
 |  | ELSE |  | ;否则，将使用BIOS的功能调用
 |  |  | MOV AH, 10H
 |  |  | INT 16H
 |  | ENDIF
 |  | ENDM

在引用宏INPUT时，汇编程序会根据DOS是否为0来生成调用不同输入功能的程序段。

方法2：使用条件汇编伪指令IFDEF
INPUT | MACRO
 | IFDEF | DOS | ;当定义了DOS，则使用DOS的功能调用
 |  | MOV AH, 1H
 |  | INT 21H
 | ELSE |  | ;否则，将使用BIOS的功能调用
 |  | MOV AH, 10H
 |  | INT 16H
 | ENDIF
 | ENDM

在引用宏INPUT时，汇编程序会根据符号DOS是否已定义来生成调用不同输入功能的程序段。

例9.15 编写一个可用功能调用输入字符的宏定义。

解：
 | READCH | MACRO char
 |  | MOV | AH, 1H
 |  | INT | 21H | ;接受一个字符，并存入AL中
 |  | IFNB | <char> | ;若参数char有实参与之对应
 |  |  | IFDIF <char>, <AL> | ;若参数char≠AL，则把所输入字符保存到实参中
 |  |  | MOV char, AL
 |  |  | ENDIF
 |  | ENDIF
 |  | ENDM


9.6 宏的扩展——REPEAT/WHILE

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的扩充

9.6 宏的扩充

MASM 6.11编程系统对宏定义及其相关语句进行了一定程度的扩充。虽然这些扩充给编程带来了一些方便，但它们不一定能被其它的汇编语言编程系统所接受，所以，程序员在使用这些方便的扩充功能时，要注意到可能带来的限制。

下面介绍MASM 6.11编程系统对宏及其相关语句的扩充。

9.6.1 宏定义形式

在MASM 6.11编程系统中，其宏定义的一般形式如下：

宏名 | MACRO [参数1[:tag]] [，参数2[:tag]...]
 | [LOCAL varlist] … [EXITM [value]] ENDM | ;宏定义体内的局部变量和标号 ;宏的定义体

对上述宏定义的说明与9.1.1节中的说明完全一致，其需要增加的说明如下：

tag—— | 其值可以是REQ、=<缺省值>或VARARG
REQ | 指定该参数是不可缺少。在宏引用时，若该参数不对应某个“实参”，那么，汇编程序会报错；
=<缺省值> | 在宏引用时，若不指定该参数所对应的“实参”，那么，该参数就取其缺省值；
VARARG | 该参数对应一个可变长的实参表，各实参之间用逗号分开；若参数的属性指定为VARARG，那么，该参数一定要是最后一个参数。 有关该属性的应用，请见随后9.6.7节中的举例。
value—— | 宏功能的返回值，其为可选项。

9.6.2 重复伪指令REPEAT

重复伪指令REPEAT与前面9.4.1节中伪指令REPT在功能和使用方式方面完全一致，设置该伪指令的主要原因是保证与先前版本的兼容性。

伪指令REPEAT的使用方式如下：

REPEAT | 数值表达式
 | 语句序列 | ;被重复的汇编语言语句
 | ENDM

9.6.3 循环伪指令WHILE

循环伪指令WHILE的使用方式如下：

WHILE | Exp
 | 语句序列 | ;被重复的汇编语言语句
 | ENDM

其功能是先判断表达式Exp是否为假(或为0)，若是，则终止该伪指令的功能，否则，循环汇编下面的指令块。表达式Exp是能在汇编时计算出其值的数值表达式。

例9.16：编写一个带有参数result和k的宏，其功能是把1+2+…+k的累加和存入result之中，其中：result是不可缺省的，k的缺省值为1。

解：

SUM | MACRO result:REQ, k:=<1>
 | LOCAL n
 | n = k
 | mov | result, 0
 | WHILE | n
 |  | add result, n n = n - 1 ENDM
 | ENDM

有了上面的宏定义，就可书写下面的宏引用来实现其相应的功能：

SUM | ax, 10 | ;寄存器ax=1+2+3+…+10
SUM | bh | ;寄存器bh=1，因为第二个形参取其缺省值
SUM | ecx, 100 | ;寄存器ecx=1+2+3+…+100
SUM | data, 20 | ;存储单元data=1+2+3+…+20


9.6 宏的扩展——FOR/FORC

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的扩充 → 循环伪指令FOR

9.6.4 循环伪指令FOR

循环伪指令FOR与9.4.2节中伪指令IRP在功能上完全一致，设置该伪指令的原因也是为了保证与先前版本的兼容性。

伪指令FOR的使用方式如下：

FOR | parameter[:REQ|:=<default>], <argument [, argument]...>
 | 语句序列 | ;被重复的汇编语言语句
 | ENDM

其中各参数的说明如下：

parameter | 一个合法的标识符，它依次取后面参数表中的值。在指令序列中，该变量的每次出现都用其值所替换；
:REQ | 说明该变量的取值不能为空；
:=<default> | 指定该变量的缺省值，若后面的参数表缺省某个参数(用连续的逗号)，这时，该循环变量将取其缺省值；
Argument | 参数表中可含有文本、符号、字符串或数值常量，每个参数之间要用逗号分割。

例如：

FOR | data:=<?>, <"123", , 21, 0>
 | DB data
 | ENDM
 | ……
FOR | reg:REQ, <ax, bx, dx>
 | push reg
 | ENDM

该语句在宏展开时，将得到下列语句：

DB　"123"
DB　 ?
DB　 21
DB　 0
……
push　 ax
push　 bx
push　 dx

9.6.5 循环伪指令FORC

循环伪指令FOR与9.4.3节中伪指令IRPC在功能上完全一致，它也是为保证与先前版本的兼容性而设置的。

伪指令FORC的使用方式如下：

FORC | parameter, <string>
 | 语句序列 | ;被重复的汇编语言语句
 | ENDM

其中各参数的说明如下：

parameter： | 一个合法的标识符，它依次取字符串中的每个字符。在语句序列中，该变量的每次出现都用其值所替换；
String： | 一个字符串或被定义为字符串的符号名，字符串中的空格也被算为一个字符。括号"<"、">"是必不可少的。

例如：

FORC | data, <1?3> DB data ENDM
 | ……
FORC | reg, <abd> push reg&x ENDM

该语句在宏展开时，将得到下列语句：

DB　1
DB　?
DB　3
……
push　ax
push　bx
push　dx


9.6 宏的扩展——GOTO

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的扩充 → 转移伪指令GOTO

9.6.6 转移伪指令GOTO

转移伪指令GOTO用于实现宏定义体内的转移功能，其使用方式如下：

      GOTO　标号
      …
:标号　　　　　;标号后不能写指令，但可写注释
      …

该伪指令的功能是使汇编程序转移到“标号”处汇编，它只能在宏定义MACRO、REPEAT、WHILE、FOR和FORC等语句块内使用，该标号也只在该语句块内有效。

9.6.7 宏扩充的举例

例9.17：编写一个给任意寄存器或存储单元清零的宏定义。

解：

Clear | list : VARARG
 | FOR | data:REQ, <list>
 |  | mov data, 0 ENDM
 | ENDM

有了上面的宏定义，下面的二个宏引用都是正确的，尽管宏引用时所带的参数个数是不同的。这正是参数属性VARARG的作用所在。

Clear ax, bx
……
Clear si, cl, MemVar, edx　　　　　　;MemVar是内存变量，任意类型都可以

这二个宏引用展开时所得到的指令如下：

mov　ax, 0
mov　bx, 0
……
mov　si, 0
mov　cl, 0
mov　MemVar, 0
mov　edx, 0


9.6 宏的扩展——系统定义的宏

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 宏的扩充 → 系统定义的宏

9.6.8 系统定义的宏

MASM 6.11系统定义了大量的标准宏，程序员能很方便地使用它们。在使用这些系统宏之前，要象C语言那样用伪指令INCLUDE把有关“宏库”文件包含在用户的源程序中。主要的系统宏库文件有：DOS.INC和BIOS.INC，它们存放在系统的include子目录中。

例9.18：使用系统宏定义，编写从键盘上读取一个字符。

解：

include dos.inc　　　　 ;把系统宏定义文件包含在源程序之中
……
@getchar 1, 1　　　　　;引用系统宏定义
……

下面是系统宏@getchar的使用参数描述和定义，其它系统宏的有关信息请参阅相关的宏定义文件。

1、系统宏@GetChar的使用说明

宏的功能： | 从键盘读字符
使用语法： | @GetChar [echo] [，[break] [，clearbuf]]
参数说明： | <echo> | ;常量，非零表示“回显”，缺省值为“回显”
 | <break> | ;常量，非零表示接受“^C”，缺省值为“接受”
 | <clearbuf> | ;常量，非零表示清键盘缓冲区，缺省值为“不清”
返回参数： | AL＝ASCII码
内容破坏： | AX，DL(若回显，且不接受^C)
参见内容： | INT 21h — 01h、07h、08h和0Ch，@GetStr

2、系统宏@GetChar的定义

该宏定义在宏库文件dos.inc中，其具体宏定义如下：

@GetChar

 MACRO　ech:=<1>, cc:=<1>, clear:=<0>
LOCAL　funct, disp
disp = 1
IF ech

IF cc

funct = 01h　　　　;使用功能1

ELSE

funct = 07h　　　　;使用功能7
disp = 02h　　　　 ;设置需要回显标志

ENDIF

ELSE

IF cc

funct = 08h　　　　;使用功能8

ELSE

funct = 07h　　　　;使用功能7

ENDIF

ENDIF
IFE clear

mov　ah, funct　　　;置功能号

ELSE

mov　ah, 0Ch　　　　;先清输入缓冲区，再接受键盘输入
mov　al, funct

ENDIF
Int　21h　　　　　　　　　;调用DOS功能中断
IF disp EQ 02h　　　　　;检查是否需要回显

mov　dl, al
mov　ah, 02h
int　21h

ENDIF
ENDM

对于上面的宏定义，程序员完全可以把它修改成其它形式的宏定义。


9.7 习题

1 宏的定义
1 宏的定义
2 宏的引用
2 宏的引用
3 宏的参数传递方式
3 宏的参数传递方式
4 宏的嵌套定义
4 宏的嵌套定义
5 宏与子程序的区别
5 宏与子程序的区别
1 连接运算符
1 连接运算符
2 字符串整体传递运算符
2 字符串整体传递运算符
3 字符转义运算符
3 字符转义运算符
4 计算表达式运算符
4 计算表达式运算符
1 局部标号伪指令
1 局部标号伪指令
2 取消宏定义伪指令
2 取消宏定义伪指令
3 中止宏扩展伪指令
3 中止宏扩展伪指令
1 伪指令REPT
1 伪指令REPT
2 伪指令IRP
2 伪指令IRP
3 伪指令IRPC
3 伪指令IRPC
1 条件汇编伪指令的功能
1 条件汇编伪指令的功能
2 条件汇编伪指令的举例
2 条件汇编伪指令的举例
1 宏定义形式
1 宏定义形式
2 重复伪指令REPEAT
2 重复伪指令REPEAT
3 循环伪指令WHILE
3 循环伪指令WHILE
4 循环伪指令FOR
4 循环伪指令FOR
5 循环伪指令FORC
5 循环伪指令FORC
6 转移伪指令GOTO
6 转移伪指令GOTO
7 宏扩充的举例
7 宏扩充的举例
8 系统定义的宏
8 系统定义的宏
本章的学习内容和要求
本章的学习内容和要求
9.1 宏的定义和引用
9.1 宏的定义和引用
9.2 宏参数的特殊运算符
9.2 宏参数的特殊运算符
9.3 与宏有关的伪指令
9.3 与宏有关的伪指令
9.4 重复汇编伪指令
9.4 重复汇编伪指令
9.5 条件汇编伪指令
9.5 条件汇编伪指令
9.6 宏的扩充
9.6 宏的扩充
9.7 习题
9.7 习题

→ 习题

9.7 习题

9.1、在宏定义时，使用的关键字是什么？宏名是否需要成对出现？

9.2、在宏引用时，是否要求实参与形参的个数相等？若不要求，请简述当二者个数不一致时，会出现什么情况。

9.3、宏和子程序的主要区别有哪些？一般在什么情况下选用宏较好，在什么情况下选用子程序较好？

9.4、宏的参数如何传入宏定义体的？宏的参数传递与子程序的参数传递有哪些区别？

9.5、在有标号的宏定义体中，为什么最好使用LOCAL伪指令来说明标号？它在宏定义体中应处于什么位置？

9.6、子程序和宏中的LOCAL伪指令的作用有哪些不同？

9.7、编写32位相加的宏ADD32，它把32位寄存器组BX-AX加到DX-CX中。

9.8、编写符号扩展的宏CBD，它将存于AL中的有符号数扩展成ECX-EBX中64位有符号数(其中：ECX是64位有符号数的高位)。

9.9、编写字母大小写互换的宏Exchange，其有一个形参，允许字节型的寄存器或存储单元作为实参来引用。

9.10、编写一个宏AddList Para1, Para2, num，其功能是将从Para2开始的内存单元的值加到以Para1开始的内存单元中，num是相加的字节数。

9.11、编写一个宏SUM Data, Length, Result，其功能是求从Data开始的字节累加和，并把结果存入字类型参数Result中，Length是需要累加的字节数。

9.12、编写一个宏来定义26个大写字母表。

9.13、编写一个宏，它产生n条NOP指令，其中n是宏的形式参数。

9.14、INCLUDE指示符的作用是什么?

9.15、编写只有一个形式参数的宏PRINT，其具体功能如下：

1)、若引用时带有参数，则在屏幕上显示其参数字符，如：PRINT 'A'，则显示字符'A'；
2)、若引用时不带实参，则显示回车和换行，如：PRINT。
提示：用IFB或IFNB语句来测试是否有参数。


10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

第10章 应用程序的设计

在前面各章节中，我们侧重介绍了汇编语言程序设计中各组成部分的作用，本章的重点是对前面所学知识的综合运用。希望通过各种不同类型的例子，使读者能够掌握用汇编语言编程的基本技巧。

10.1 字符串的处理程序

字符或字符串是一类重要的非数值计算的处理对象。许多编辑软件都具有字符串查找、替换、大小写的转换、单词的自动识别等功能，网络上的信息搜索也是现在一种常用的功能等，这些功能的实现无疑都要涉及到字符串的处理功能。

为了方便对字符串的处理，各种常用的编程环境也都给予了足够的支持。如：C语言编程环境提供了大量处理字符串的标准函数，象strlen、strcmp和strcpy等函数；C++、VC或VB等编程环境提供了字符串类String等。这些函数或类大大方便了程序员的编程。

在计算机系统内，为了加快字符串的处理，在其指令系统中设置了多条处理字符串的指令，其详细内容请参阅第5.2.11节中的介绍。

下面我们将通过几个例子来学习汇编语言处理字符串的方法。

例10.1 编写一个求字符串长度的子程序Strlen，要求字符串的首地址为入口参数，且以ASCII码0为结束符，CX为出口参数，其存放该字符串的长度。

解：
 | .MODEL SMALL, C
 | .DATA
 | buff | DB "This is a example.", 0
 | .CODE
 | Strlen | PROC USES AX BX, String:PTR BYTE
 |  | MOV | BX, String
 |  | XOR | CX, CX
 |  | MOV | AL, [BX]
 |  | .WHILE AL!=0
 |  | INC | CX
 |  | INC | BX
 |  | MOV | AL, [BX]
 |  | .ENDW
 |  | RET
 | Strlen | ENDP
 |  | .STARTUP
 |  | INVOKE Strlen, ADDR buff
 |  | .EXIT 0
 |  | END


10.1 字符串的处理程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 字符串的处理 → 字符串的处理程序

例10.2 编写一个把字符串中的所有小写字符转换成大写字符的子程序Strupr，要求字符串的首地址和结束符为其入口参数。

解：
 | .MODEL SMALL, C
 | .DATA
 | buff | DB "This is a example.", 0
 | .CODE
 | Strupr | PROC USES AX BX, String:PTR BYTE, Tail:BYTE
 |  | MOV | BX, String
 |  | .REPEAT
 |  | MOV | AL, [BX]
 |  | .IF AL>='a' && AL<='z'
 |  | SUB | AL, 20H
 |  | MOV | [BX], AL
 |  | .ENDIF
 |  | INC | BX
 |  | .UNTIL | AL==Tail
 |  | RET
 | Strupr | ENDP
 |  | .STARTUP
 |  | INVOKE Strupr, ADDR buff, 0
 |  | .EXIT 0
 |  | END

例10.3 编写一个从字符串中拷贝子串的子程序Strncpy，它有四个参数str1、str2、idx和num，其具体功能为把字符串str2中从第idx个(从0开始记数)字符开始、num个字符传送给str1，字符串str1和str2都是以ASCII码0为结束符。

解：
 | .MODEL | SMALL, C
 | .DATA
 | str1 | DB "12345ABCDEF", 0
 | str2 | DB 20 DUP('A')
 | .CODE
 | Strlen | PROC USES AX BX, String:PTR BYTE
 |  | …… |  | ;参见例10.1
 | Strlen | ENDP
 | Strncpy | PROC USES AX CX DI SI DS ES, str1:FAR PTR BYTE, str2:FAR PTR BYTE, idx:WORD, num:WORD
 |  | LES | DI, str1
 |  | LDS | SI, str2 | ;取两个字符串的首地址
 |  | INVOKE | Strlen, SI | ;计算源字符串的长度，在CX中
 |  | MOV | AX, idx
 |  | .IF AX >= CX |  | ;若字符起点就超过源串的长度
 |  | MOV | BYTE PTR ES:[DI], 0 | ;拷贝的字符串为“空”
 |  | JMP | over
 |  | .ENDIF
 |  | ADD | SI, AX | ;定源串中字符的起点SI
 |  | MOV | CX, num
 |  | CLD
 |  | .REPEAT
 |  | LODSB
 |  | STOSB
 |  | .UNTILCXZ AL==0
 |  | .IF AL!=0 |  | ;设置目标串的结束符
 |  | MOV | BYTE PTR[DI], 0
 |  | .ENDIF
 | over: | RET
 | Strncpy | ENDP
 |  | .STARTUP
 |  | INVOKE | Strncpy, ADDR str2, ADDR str1, 3, 5
 |  | .EXIT | 0
 |  | END


10.1 字符串的处理程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 字符串的处理 → 字符串的处理程序

例10.4 编写一个把字符串中空格和TAB压缩掉的子程序Compress，字符串String是以ASCII码0为结束符。

解：
 | .MODEL | SMALL, C
 | .DATA
 | SPACE | EQU 20H
 | TAB | EQU 9H
 | Buff | DB "12 3 4 Ab cdef", 0
 | .CODE
 | Compress | PROC USES AX BX SI DS, String:FAR PTR BYTE
 |  | LDS | SI, String |  | ;SI用于扫描字符串的指针
 |  | MOV | BX, SI |  | ;BX用于存放结果的指针
 |  | .REPEAT
 |  | MOV | AL, [SI]
 |  | INC | SI
 |  | .IF AL!=SPACE && AL!=TAB
 |  |  | MOV | [BX], AL
 |  |  | INC | BX |
 |  | .ENDIF
 |  | .UNTIL AL==0
 |  | RET
 | Compress | ENDP
 |  | .STARTUP
 |  | INVOKE Compress, ADDR Buff
 |  | .EXIT | 0
 |  | END

从上面四个例子，我们不难看出处理字符串的一般方法，感兴趣的读者可自行编写实现字符串变小写、整体拷贝、逆转和查找等功能的子程序，甚至还可以建立起自己的字符串处理库文件。


10.2 数据的分类统计程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据的分类统计程序

10.2 数据的分类统计程序

数据的分类和统计也是一类非数值计算，数据的分类统计方法在例6.10中已介绍，下面通过一个例子介绍数据的分类存储问题。

例10.5 统计从地址0040H:0000H开始的100个字中，把正数和负数按照它们先后出现的次序分别存储在缓冲区Data1和Data2，并把每类的个数存入相应缓冲区的第一个字单元中。

解：由于在指定地址之后的100个字中，可能存在全是正数或负数的情况，所以，缓冲区Data1和Data2的容量都应是100个字。
 | .MODEL SMALL
 | .DATA
 | Num = 100
 | Data1 | DW ?, Num dup(?)
 | Data2 | DW ?, Num dup(?)
 | .CODE
 | .STARTUP
 | MOV | AX, 40H
 | MOV | ES, AX
 | LEA | SI, Data1+2 | ;指向存储正数的缓冲区
 | LEA | DI, Data2+2 | ;指向存储负数的缓冲区
 | XOR | BX, BX | ;BX用于扫描存储单元
 | MOV | CX, 100 | ;字符个数
 | .REPEAT
 | MOV | AX, ES:[BX]
 | ADD | BX, 2
 | CMP | AX, 0
 | .CONTINUE .IF ZERO?
 | JL | next1
 | MOV | [SI], AX | ;向正数缓冲区内存储数据
 | ADD | SI, 2
 | .CONTINUE
next1:
 | MOV | [DI], AX | ;向负数缓冲区内存储数据
 | ADD | DI, 2
 | .UNTILCXZ
 | SUB | SI, OFFSET Data1+2
 | SUB | DI, OFFSET Data2+2
 | SHR | SI, 1
 | SHR | DI, 1
 | MOV | Data1, SI
 | MOV | Data2, DI | ;把每类的统计个数存入缓冲区的第一个字单元
 | .EXIT | 0
 | END


10.2 分类统计程序举例

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据的分类统计 → 分类统计程序举例

例10.6 用键盘输入任意一字符串，分类统计该字符串中每个数字和字母的出现次数。

解：
 | .MODEL SMALL
 | .DATA
 | N = 80
 | Buff | DB N, ?, N DUP(?)
 | Num | DW 36 DUP(0) | ;每个字用于存放'0'~'9'，'A'~'Z'出现的个数
 | .CODE
 | .STARTUP
 | LEA | DX, Buff
 | MOV | AH, 0AH
 | INT | 21H | ;输入一个字符串
 | XOR | CH, CH
 | MOV | CL, Buff+1 | ;CX=输入字符串的个数
 | LEA | SI, Buff+2
 | XOR | BX, BX
 | .REPEAT
 | MOV | BL, [SI] | ;考虑下面的思考题
 | INC | SI
 | .IF BL>='0' && BL<='9' |  | ;分类统计'0'~'9'中的每个数字的个数
 | SUB | BL, '0'
 | ADD | BX, BX
 | INC | Num[BX]
 | .CONTINUE
 | .ENDIF
 | .IF BL>='a' && BL<='z'
 | SUB | BL, 20H | ;小写变大写
 | .ENDIF
 | .IF BL>='A' && BL<='Z' |  | ;分类统计'A'~'Z'中的每个字母的个数
 | SUB | BL, 'A'-10
 | ADD | BX, BX
 | INC | Num[BX]
 | .ENDIF
 | .UNTILCXZ
 | .EXIT 0
 | END

思考题：在本例中，用指令“MOV BL, [SI]”来把当前检测的字符存入BL，当然，我们也可以用AL来代替BL，有关指令要作相应的改动，但这样做，会更方便吗？希望读者能知道：为什么要用BL，而不用AL？


10.3 数据转换程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据转换程序

10.3 数据转换程序

数据类型转换是输入输出过程中经常遇到的问题。输入时，计算机系统要把用户从键盘上输入的字符串转变成相应的数值，并存储在内存中；输出时，要把计算机内部的二进制数据形式转换成相应的十进制字符串，然后再输出。

在高级语言编程环境中，程序员能用各种输入输出语句，按一定的格式进行交互式操作，很少或根本不关心输入输出是如何实现的。有的程序员甚至认为其输入的就是十进制数值，输出数据也就是把内存中存储的数据直接输出出来。其实，输入输出过程并不是如此简单，计算机系统要进行复杂而又细致的数据类型转换和格式化等工作。

本节试图通过用汇编语言实现数据类型的转换来反映输入输出的本来面目，使程序员在用高级语言编程时，对其输入输出语句的实现过程有所了解，也知道有别人(或编译程序)帮他完成了输入输出的准备工作。

例10.7 编写一个程序，它能把字类型变量的数值以十进制形式输出出来。若该数值为负数，则需要输出负号"-"，否则，不输出符号。

解：

鉴于按二进制输出的特殊性，我们可以把它优化成例10.8的形式，按十六进制输出也可以按“四位二进制对应一位十六进制”的规则进行优化的。


10.3 数据转换程序举例

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据转换 → 数据转换程序举例

例10.8 编写程序，它能把字类型变量的数值以二、八进制或十六进制形式输出出来。若该数值为负数，则需要输出负号"-"，否则，不输出符号。

解：

一、按二进制形式输出的程序

二、按八进制形式输出的程序

三、按十六进制形式输出的程序


10.3 数据转换程序举例

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据转换 → 数据转换程序举例

例10.7是用“用16位除10”的方法从低向高依次得到每位的数值，但若待输出的数据是32位，用10除之后，其商很可能会超过16位，所以，不能简单地引用例10.7的方法来输出32位二进制。

假设：32位二进制数Z为A×216＋B，其中：A和B都是16位二进制数。 用10去除A，得：A＝A1×10＋A2，于是，

              (1)

假设A2×216＋B被10除后所得的商和余数分别为B1和C1(B1≥0，C1≥0)。

利用式(1)和“A2<10”，我们不难看出：Z的个位就是C1和B1<216。

令Z1＝A1×216＋B1，显然，Z1就是Z/10所得到的商。

对于Z1，再利用式(1)得到商Z2和C2。……，重复上面的步骤，直到所得商为0为止。

下面的例10.9就是利用上面方法来输出32位二进制数值。

例10.9 编写一个子程序，该子程序能把32位二进制变量的数值以十进制形式输出出来。若该数值为负数，则需要输出负号"-"，否则，不输出符号。

解：
 | .MODEL SMALL, C
 | .DATA
 | CR = 13
 | LF = 10
 | Data1 | DD 908976789
 | .CODE
 | ;子程序Display是按十进制输出32位二进制数值SOURCE
 | Display | PROC USES AX BX CX DX SI DI SOURCE:DWORD
 |  | LOCAL | FLAG:BYTE | ;定义一个字节类型的局部变量FLAG
 |  | MOV | BX, WORD PTR [SOURCE]
 |  | MOV | CX, WORD PTR [SOURCE+2]
 |  | MOV | FLAG, 0 | ;FLAG=0——正数
 |  | CMP | CX, 0
 |  | JGE | next
 |  | INC | FLAG | ;FLAG=1——负数
 |  | NOT | BX
 |  | NOT | CX
 |  | ADD | BX, 1 | ;能否用指令INC BX?
 |  | ADC | CX, 0 | ;上四条指令把32位数CX-BX变为正数
 | next:
 |  | XOR | DI, DI | ;压入堆栈字符的个数
 |  | MOV | SI,10 | ;用10来除
 |  | .REPEAT |  | ;本循环把32位二进制数转换成十进制
 |  | XOR | DX, DX | ;数的字符串存入堆栈之中
 |  | MOV | AX, CX
 |  | DIV | SI
 |  | MOV | CX, AX
 |  | MOV | AX, BX
 |  | DIV | SI
 |  | ADD | DL, '0'
 |  | PUSH | DX
 |  | INC | DI
 |  | MOV | BX, AX
 |  | .UNTIL | BX==0 && CX==0
 |  | .IF FLAG==1 |  | ;判断前面转换的数是否为负数
 |  | MOV | AL, '-' | ;若是，把符号'-'压入堆栈
 |  | PUSH | AX
 |  | INC | DI
 |  | .ENDIF |
 |  | MOV | CX, DI
 |  | .REPEAT |  | ;本循环把堆栈中的字符串显示出来
 |  | POP | DX
 |  | MOV | AH, 2
 |  | INT | 21H
 |  | .UNTILCXZ
 |  | MOV | DL, CR | ;下面六条指令显示回车、换行
 |  | MOV | AH, 2
 |  | INT | 21H
 |  | MOV | DL, LF
 |  | MOV | AH, 2
 |  | INT | 21H
 |  | RET
 | Display | ENDP
 |  | .STARTUP
 |  | INVOKE | Display, Data1
 |  | INVOKE | Display, -123456789
 |  | .EXIT | 0
 |  | END


10.3 数据转换程序举例

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 数据转换 → 数据转换程序

例10.10 编写一个程序，它能把用键盘输入的字符串转化成相应的数值。具体功能如下：
1、 | 输入的数据字符串可以带正、负符号，如：1234、+1234或-1234；
2、 | 字符串的最后一个字符表示数据的进制，默认的进制为十进制，如：1234H表示十六进制数1234，1234为十进制数；
3、 | 对于任何进制的数据，当遇到一个非进制范围内的字符时，则显示出错信息，并以数值为其转换结果来结束该类型转换过程。

解：


10.4 文件操作程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 文件操作程序

10.4 文件操作程序

有关目录和顺序文件的操作在第8.3.6节中已有介绍和举例，本节主要介绍对记录文件的读写方法。记录文件是指文件中的每个分量是一个结构的文件，如：Fox系列数据库管理系统中的DBF文件，该文件除了文件头是由记录文件的整体信息和各字段描述信息之外，文件的主体内容就是由同一个结构组成的。

下面通过二个例子来介绍记录文件的读写方法。

例10.11 假设有一个简单的学生结构类型student，其包括：学号、姓名和年龄等信息，要求编写一个程序，该程序接受从键盘输入的学生记录信息，并把它们保存在文件students.dat之中。

解：
 | .MODEL SMALL, C
 | .486
 | student | STRUCT
 |  | id | DW ?
 |  | sname | DB 10 DUP(?)
 |  | age | DB ?
 | student | ENDS
 | .DATA
 | fname | DB "Students.dat",0
 | msg1 | DB "Id:$"
 | msg2 | DB "Name:$"
 | msg3 | DB "Age:$"
 | msg4 | DB "Continue?$"
 | msg5 | DB "Fail to create file$"
 | CRLF | DB 0AH, 0DH, "$"
 | buff | DB ?, ?, 11 DUP(?)
 | peason | STUDENT <>
 | .CODE
 | DispMsg | PROC USES AX DX, Msg:PTR BYTE |  | ;显示字符串Msg
 |  | …… |  | ;参见例10.7
 | DispMsg | ENDP
 | ;程序功能：把字符串Data转化成数值，不考虑负数。当遇到非法字符时，则结束转换操作；
 | ;入口参数：Data为字符串的首地址，Len为该字符串的长度；
 | ;出口参数：数值存放在AX中。
 | GetData | PROC USES BX CX SI, Len:BYTE, Data:PTR BYPE
 |  | XOR | CX, CX
 |  | MOV | CL, Len
 |  | MOV | SI, Data
 |  | XOR | AX, AX
 |  | XOR | BX, BX
 |  | .REPEAT
 |  | MOV | BL, [SI]
 |  | SUB | BL, '0'
 |  | .BREAK .IF BL<0 || BL>9 |  | ;判断当前数值是否在0~9之间
 |  | IMUL | AX, 10
 |  | ADD | AX, BX
 |  | INC | SI
 |  | .UNTILCXZ
 |  | RET
 | GetData | ENDP
 | ;程序功能：读取指定长度的字符串，在输入前，显示有关输入内容的提示信息；
 | ;入口参数：读入字符串的长度为Len，提示信息的首地址为MSG；
 | ;出口参数：读入信息(字符串)存放缓冲区buff中。
 | GetInfo | PROC USES AX DX, Len:BYTE, Msg:PTR BYTE
 |  | INVOKE | DispMsg, Msg | ;显示提示信息
 |  | MOV | AL, Len
 |  | MOV | buff, AL
 |  | MOV | AH, 0AH
 |  | LEA | DX, buff
 |  | INT | 21H
 |  | INVOKE | DispMsg, ADDR CRLF | ;显示回车、换行
 |  | RET
 | GetInfo | ENDP
 |  | .STARTUP
 |  | MOV | AX, DS
 |  | MOV | ES, AX
 |  | LEA | DX, fname
 |  | MOV | CX, 20H
 |  | MOV | AH, 3CH
 |  | INT | 21H | ;创建文件
 |  | .IF CARRY? |  | ;若创建失败，则显示失败信息
 |  | INVOKE DispMsg, ADDR msg5
 |  | JMP over
 |  | .ENDIF
 |  | MOV | BX, AX | ;把句柄存入BX，为后面使用作准备
 | again:
 |  | INVOKE | DispMsg, ADDR CRLF | ;显示回车、换行
 |  | INVOKE | GetInfo, 5, ADDR msg1 | ;读取学号(假定学号为4位整数)
 |  | INVOKE | GetData, 4, ADDR buff+2 | ;把学号字符串转化成数值
 |  | MOV | peason.id, AX | ;把数值型学号存入学号字段
 |  | INVOKE | GetInfo, 11, ADDR msg2 | ;读取姓名(假定姓名为10个字符)
 |  | MOV | CX, 10
 |  | MOV | AL, ' '
 |  | LEA | DI, peason.sname
 |  | REP | STOSB | ;先置姓名字段为10个空格
 |  | MOV | CL, buff+1
 |  | MOV | SI, OFFSET buff+2
 |  | LEA | DI, peason.sname
 |  | REP | MOVSB | ;把输入的姓名存入姓名字段
 |  | INVOKE | GetInfo, 3, ADDR msg3 | ;读取年龄(假定年龄为2位整数)
 |  | INVOKE | GetData, 2, ADDR buff+2 | ;把年龄字符串转化成数值
 |  | MOV | peason.age, AL | ;把数值型年龄存入年龄字段
 |  | MOV | CX, SIZE peason
 |  | LEA | DX, peason
 |  | MOV | AH, 40H
 |  | INT | 21H | ;把学生记录写入文件
 |  | INVOKE | DispMsg, ADDR msg4 | ;提示是否继续输入
 |  | MOV | AH, 1
 |  | INT | 21H
 |  | AND | AL, 0DFH
 |  | CMP | AL, 'Y'
 |  | JZ | again | ;若按y或Y，则继续输入
 |  | MOV | AH, 3EH
 |  | INT | 21H
 | over:
 |  | .EXIT | 0
 |  | END


10.4 文件操作程序举例

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 文件操作程序 → 文件操作程序举例

例10.12 编写一个程序显示由例10.11建立的记录文件students.dat中的学生信息。

解：
 | .MODEL SMALL,C
 | student | STRUCT
 |  | id | DW ?
 |  | sname | DB 10 DUP(?)
 |  | age | DB ?
 | student | ENDS
 | .DATA
 | fname | DB "Students.dat",0
 | id1 | DB "Id:", 4 dup(?), 0dh, 0ah, "$"
 | name1 | DB "Name:", 10 dup(' '), 0dh, 0ah, "$"
 | age1 | DB "Age:", 2 dup(?), 0dh, 0ah, "$"
 | msg1 | DB "Fail to open file$"
 | peason | student <>
 | .CODE
 | DispMsg | PROC USES AX DX, Msg:PTR BYTE |  | ;显示字符串Msg
 |  | …… |  | ;参见例10.7
 | DispMsg | ENDP
 | ;程序功能：把数据Data转换成长度为Len的字符串； ;入口参数：待转换数据Data，转换成字符串的长度为Len，存放字符串的首地址为PStr； ;出口参数：读入信息(字符串)存放缓冲区buff中。
 | GetStr | PROC USES AX CX DX DI, Data:WORD, Len:WORD, PStr:PTR BYTE
 |  | MOV | CX, Len
 |  | MOV | DI, PStr
 |  | MOV | AL, ' '
 |  | REP | STOSB | ;把存放字符串的缓冲区填充为空格
 |  | MOV | DI, PStr
 |  | ADD | DI, Len
 |  | DEC | DI | ;确定最后一个字符在缓冲区中的位置
 |  | MOV | AX, Data
 |  | MOV | CX, 10
 |  | .REPEAT
 |  | XOR | DX, DX
 |  | IDIV | CX | ;除10，从低位向高位求得每一位
 |  | ADD | DL, '0' | ;把余数转变成字符，然后存放目标单元
 |  | MOV | [DI], DL
 |  | DEC | DI
 |  | .UNTIL AX==0
 |  | RET
 | GetStr | ENDP
 |  | .STARTUP
 |  | MOV | AX, DS
 |  | MOV | ES, AX
 |  | LEA | DX, fname
 |  | MOV | AL, 0H
 |  | MOV | AH, 3DH
 |  | INT | 21H | ;以“只读”方式打开指定的文件
 |  | .IF CARRY? |  | ;若创建失败，则显示失败信息
 |  | INVOKE DispMsg, ADDR msg1
 |  | JMP over
 |  | .ENDIF
 |  | MOV | BX, AX | ;把句柄存入BX，为后面使用作准备
 | again:
 |  | MOV | CX, SIZE peason
 |  | LEA | DX, peason
 |  | MOV | AH, 3FH
 |  | INT | 21H | ;从文件中读出一个记录
 |  | .IF CARRY? || AX==0 |  | ;若读记录出错或遇到文件尾，结束
 |  | JMP | close
 |  | .ENDIF
 |  | INVOKE | GetStr, peason.id, 4, ADDR Id1+3 | ;把“学号”转换成字符串
 |  | INVOKE | DispMsg, ADDR Id1 | ;显示“学号”字符串
 |  | MOV | CX, 10
 |  | LEA | SI, peason.sname
 |  | LEA | DI, Name1+5
 |  | REP | MOVSB | ;把“姓名”转移到显示区
 |  | INVOKE | DispMsg, ADDR Name1 | ;显示“姓名”字符串
 |  | INVOKE | Getstr, peason.age, 2, ADDR Age1+4 | ;把“年龄”转换成字符串
 |  | INVOKE | Dispmsg, ADDR Age1 | ;显示“年龄”字符串
 |  | JMP | again
 | close:
 |  | MOV | AH, 3EH
 |  | INT | 21H | ;关闭当前打开的文件
 | over:
 |  | .EXIT | 0
 |  | END

从例10.11和10.12，我们不难掌握记录文件的读写方法。有兴趣的读者，还可以利用文件指针的定位来指定读写某个具体的记录。


10.5 动态数据的编程

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 动态数据的编程

10.5 动态数据的编程

动态数据结构是一种常用的数据结构，在事先不知道所处理数据容量的情况，用动态数据是一种行之有效的方法，也为许多C语言程序员所采用。在汇编语言中，我们也可以采用动态数据的方式来存储数据，并进行链表的遍历。

为了使读者尽快理解本例的功能，我们把与之相似功能的C语言程序书写如下：

#include <stdio.h>
#include <alloc.h>
struct link {
int data;
struct link *next;
};
void main( )
{struct link *head=NULL, *temp, *pt;
int i;
for (i = 20; i > 0; i--) { | ;生成20个结点的链表
temp = (struct link *)calloc(1,sizeof(struct link)); | ;申请一个结点的空间
if (temp == NULL) break; | ;若分配内存失败
temp->data = i;
temp->next = NULL;
if (head == NULL) head = temp;
else pt->next = temp;
pt = temp;
}
while (head != NULL) { | ;遍历结点并输出其数据字段
printf("%d\n",head->data);
pt = head; head = head->next; free(pt);
}
}

例10.13 编写一个程序用动态链表存储20，19，……，1，并用遍历链表的方法来显示每个结点的数值。

解：显示解答


10.6 COM文件的编程

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ COM文件的编程

10.6 COM文件的编程

COM文件和EXE文件都是可执行文件，最典型的COM文件是Command.COM。COM文件的主要特点如下：

1、COM文件只有一个段，其字节数不会超过64K；
2、当操作系统装入COM文件时，四个段寄存器(CS、DS、ES和SS)都 用PSP的段值来初始化；
3、必须用伪指令ORG 100H来说明空出前256个字节。

例10.14 编写一个显示字符串“Hello”的COM类型的程序。

解： |
 | CSEG SEGMENT 'CODE'
 |  | ORG | 100H | ;空出前256个字节
 | start: | LEA | DX, MSG
 |  | MOV | AH, 09H
 |  | INT | 21H
 |  | MOV | AX, 4C00H
 |  | INT | 21H
 |  | MSG DB "Hello$" |  | ;定义字符串
 | CSEG | ENDS
 |  | END start

对上面程序，其生成的COM文件只有23个字节，而其EXE文件的字节数会超过1K。

在PWB编程环境下，可在Option→Project Templates→Set Project Template→在列表框中选DOS COM来指定生成COM文件。在Turbo Assember系统中，可用TASM、TLINK /T来指定生成COM文件。


10.7 驻留程序

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 驻留程序

10.7 驻留程序

驻留程序TSR(Terminate but Stay Resident)是一种特殊应用程序，它在装入内存运行后，其部分代码仍然驻留在内存，当该段代码被激活时，它又进入运行状态。常用的驻留程序是作为某个中断处理程序的一部分，其激活条件就是系统产生了此中断的中断请求。

虽然驻留程序可根据具体的需要有不同的编写方式，但其典型结构包括以下几部分：

1、 | 保存、修改中断向量表；
2、 | 程序第一次运行时的初始化部分： ◆用自己定义的地址来取代中断向量表中的原地址 ◆确定驻留代码部分的字节数 ◆用中断21H之功能31H把需要驻留代码部分驻留在内存
3、 | 驻留内存的代码部分。

例10.15 在NumLock处于“开状态”时，每按小键盘(Numeric Keypad)上的数字键，给出“啪啪”响声。

解：显示解答

例10.16 编写一个驻留程序，它可显示当前时间的秒数。

解：显示解答


10.8 程序段前缀及其应用

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 程序段前缀及其应用

10.8 程序段前缀及其应用

程序段前缀PSP(Program Segment Prefix)是一个具有256个字节的信息区，是可执行文件(EXE和COM)所特有的，其内容在操作系统装入该文件运行时存入。

10.8.1 程序段前缀的字段含义

PSP信息区的字段分布如下表10.1所列。

表10.1 PSP信息区的字段分布表

偏移量 | 内容含义 | 偏移量 | 内容含义
00~01H | 程序结束指令中断20H | 2E~31H | 保留
02~03H | 分配给该程序的最后段的段地址 | 32~33H | 文件句柄表的长度
04~09H | 保留 | 34~37H | 指向文件句柄表的远指针
0A~0DH | 中断22H的地址(处理终止程序) | 38~4FH | 保留
0E~11H | 中断23H的地址(处理^Break) | 50~51H | 中断21H的功能调用
12~15H | 中断24H的地址(处理严重错误) | 52~5BH | 保留
16~17H | 保留 | 5C~6BH | 参数区1
18~2BH | 缺省的文件句柄表 | 6C~7FH | 参数区2
2C~2DH | 程序环境块的段地址 | 80~FFH | 存储缺省DTA的缓冲区

PSP信息区的字段说明：

◆18~2BH字段： | 该字段内共有20个字节，每个字节存储一个文件句柄，所以，系统允许应用程序在某一时刻最多只能打开20个文件。 前5个字节存储系统标准设备的句柄，可参阅8.3.6节——系统标准设备的句柄。 若某文件需要同时打开更多的文件，则需要调整文件句柄数。即：先用中断21H之功能4AH释放内存，再用其功能67H来设置新的文件句柄数。 MOV BX, NewNum ;新的文件句柄最大数(20~65535) MOV AH, 67H INT 21H
◆ 2C~2DH字段： | 该字段存放程序运行环境的段地址，程序的缺省运行环境有160个字节，最多可达32K。该环境含有系统命令，如：COMSPEC、PATH、PROMPT和SET。
◆ 5C~6BH字段： | 该字段存放命令行的第一个参数。假设要执行下列命令：
 | Masm D:test.asm
 | 这时，04H(驱动器D)、8个字符的文件名和3个字符的后缀存放在该区域，没有存放字符的单元用空格(20H)来填充。若省缺驱动器和文件名，则第一个字节为00H，其它单元为20H。
◆ 6C~7FH字段： | 该字段存放命令行的第二个参数。假设要执行下列命令：
 | Masm D:test.asm, test.obj
 | 这时，test.obj作为第二参数存入该字段，存储方式如上字段。
◆ 80~FFH字段： | 该字段的第一个字节存放命令行参数的字符数，第二个字节为空格，从第三个字节开始存放命令行参数字符。


10.8.2 程序段前缀的应用

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 程序段前缀 → 程序段前缀的应用

10.8.2 程序段前缀的应用

了解和掌握PSP中的信息分布就是为了利用其信息。对于EXE文件，可用中断21H之功能51H获取其段地址。如：

MOV | AH, 51H
INT | 21H | ;BX=PSP的段地址
MOV | ES, BX
CMP | byte ptr ES:[80H], 0 | ;检查PSP的长度
JE | next
…

对于COM文件，因为其只有一个段，所以，可用更简单的方式来检查PSP的内容。

MOV | BX, 80H
CMP | byte ptr [BX], 0 | ;检查PSP的长度
JE | next
…

例10.17 利用PSP中的信息来终止当前程序的运行。

解：显示解答

例10.18 编写程序，显示其命令行参数信息。

解：在使用命令行时，操作系统通常允许在被装入的程序名之后附加多达127个字符(包括最后的回车符)作为其命令的参数。

本例子的目的就是要了解命令行参数的存储方法，为以后使用命令行参数作准备。

显示解答

例10.19 编写一个类型于DOS内部命令TYPE的程序，该命令的使用方法如下：

……>type 文件1，文件2，…

其功能是分别显示其参数文件1、文件2等内容，在显示文件内容前先显示文件名。显示完内容后，暂停，等待用户按任意键。若某文件不存在，则显示相应的提示信息。

解：为了较好地解答本题，我们可以对该问题按以下步骤来解决：

1、若命令行参数中的字符数为0，则结束本程序的运行；
2、从命令行参数中分离出一个文件名；
3、调用子程序，显示当前文件名、文件内容和等待用户按键；
4、重复步骤1~3，直到所有参数被处理完。

显示解答


10.9 习题

1 程序段前缀的字段含义
1 程序段前缀的字段含义
2 程序段前缀的应用
2 程序段前缀的应用
本章的学习内容和要求
本章的学习内容和要求
10.1 字符串的处理程序
10.1 字符串的处理程序
10.2 数据的分类统计程序
10.2 数据的分类统计程序
10.3 数据转换程序
10.3 数据转换程序
10.4 文件操作程序
10.4 文件操作程序
10.5 动态数据的编程
10.5 动态数据的编程
10.6 COM文件的编程
10.6 COM文件的编程
10.7 驻留程序
10.7 驻留程序
10.8 程序段前缀及其应用
10.8 程序段前缀及其应用
10.9 习题
10.9 习题

→ 习题

10.9 习题

10.1、编写一个子程序，实现字符串的逆转。如：ABCD ==> DCBA。

10.2、编写一段程序，接收4位十六进制数，然后用移位的方法把它转换成8进制数并输出在屏幕上。

10.3、编写一段程序，以十六进制显示内存0400H:1000H开始的100字，要求每行显示16个字，每字之间有空格。

10.4、简述COM文件的主要特点，它与EXE文件有哪些差异？

10.5、编写一个显示"Hello"的程序，试比较它的COM格式文件和EXE格式文件的大小。

10.6、编写一个在屏幕上连续显示"Welcome…"的信息，要求显示颜色和显示位置都是随机的。在新位置显示时，原位置的信息抹去。当按任意键时，程序结束运行。

10.7、从键盘输入一个表示年份的正整数(1~65535)，然后判断其是否为闰年。若是，则输出"Yes"，否则，输出"No"。

10.8、编写一个带命令行参数的程序Words，输出指定正文文件中的单词，假设单词为连续的字母串。
比如：words file.txt，显示文件file.txt中的每个单词

10.9、编写一个建立双向链表的程序，每接受一个整数，链表增加一个结点，当遇到负数时，结束链表结点的增加，然后从表尾向前输出各结点中的数值。

10.10、编写一个带命令行参数的程序Calc，其命令行参数是一些整数，程序输出它们之和。如果某参数为非法整数，则忽略之。
比如：Calc 120 +34 -56，则程序显示98。

10.11、在10.10的基础上，在不考虑运算符优先级的情况下，实现一个可进行算术四则运算(只有加、减、乘和除，没有括号等)的计数器，并显示其计算结果。

10.12、编写一个简单的加密程序，它把一个文件中的所有字母字符按下列规律进行转换，非字母的字符保持不变。
转换规律：'A'→'Z'，'B'→'Y'，……。

10.13、编写一个程序，它可显示BMP的图形文件。

10.14、编写一个程序，它可显示JPG的图形文件。


11.1 协处理器的数据格式

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

第11章 数值运算协处理器

数值运算协处理器(简称协处理器)是特为与微处理器协同工作而设计的，它是用于加速处理浮点数据的处理部件。对同样的浮点计算，使用该部件进行运算所花的执行时间要比用常规指令编写的最有效代码所花的时间还要少得多。在早期的计算机系统中，该部件是可选部件，但现在一般都把协处理器直接内置在CPU之中。鉴于现在Pentium处理器内部结构的特点，该处理器能同时执行一条协处理器指令和二条整数指令。

协处理器的主要产品序列有：8087、80287、80387SX、80387DX和80487SX等。

协处理器可处理的数据类型有：16位、32位和64位有符号整数，18位BCD码，32位、64位和80位浮点数。

协处理器可处理的运算有：乘法、除法、加法、减法、求平方根、部分正切、部分反正切和对数等运算。

11.1 协处理器的数据格式

在第4章，我们主要介绍了整数在内存中的存储形式，这显然不能满足实际编程的需要。数据类型的另一大类就是浮点数，浮点数在内存中的存储形式就是本节所介绍的主要内容。有关浮点数的存储格式在《计算机组成原理》中的有关章节也有详细说明，不太熟悉的读者可进行辅助阅读。

11.1.1 有符号整数

有符号数在协处理器中的应用与前面章节中所描述的方法是一致的，它是各种数据类型的基础。这些整数可分为：16位(字型)、32位(短整型)和64位(长整型)，其最高位为符号位。这些整数的数据格式如图11.1所示，它们所能表示的数据范围如表11.1所列。

(a) 字整型数据

(b) 短整型数据

(c) 长整型数据

图11.1 有符号整型数据的数据格式

表11.1 各类整型数据的表示范围

数据类型 | 范 围
字型 | -32768 ~ 32767
短整型 | -2147483648 ~ 2147483647
长整型 | -9×1018 ~ 9×1018

在汇编语言环境下，这三种整型数据的定义符分别为：DW、DD和DQ。如：

data1 | DW 2, -340 | ;16位整数
data2 | DD 321, -320 | ;短型整数
data3 | DQ -1230, 9034 | ;长型整数

11.1.2 BCD码数据

一个BCD码数据在内存中占80位，共10个字节。其最高位字节用来表示正负号，其余9个字节，每个字节内含有二个BCD码，所以，一个BCD码数据可表示18个BCD编码。BCD码的数据格式如图11.2所示。

9 … 3 2 1 0
符号字节 … …

BCD

 BCD BCD BCD

图11.2 BCD码的数据格式

关于BCD码的正负数，有如下规定：

、若最高位字节的值为0H，则表示该BCD码的值为正数；
、若最高位字节的值为80H，则表示该BCD码的值为负数。

在汇编语言环境下，BCD码数据的定义符为：DT。如：

.387
BCD1　DT　1234, -340

该说明语句决定了数据在内存中的存储形式如下：

00000000000000001234，80000000000000000340


11.1.3 浮点数

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点数的数据格式 → 浮点数

11.1.3 浮点数

在计算机中，浮点数一般由三部分组成：数值的符号位、阶码和有效数字(以后简称为尾数)。这种浮点数是用科学记数法来表示的，即：浮点数＝符号位.有效数字×2阶码。

Intel系列的协处理器支持3种形式的浮点数：短型浮点数(32位)、长型浮点数(64位)和临时浮点数(80位)，它们分别对应单精度、双精度和扩展精度浮点数。这些浮点数的数据格式都符合IEEE-754标准，它们的具体格式如图11.3所示。

图11.3　80x87中浮点数的三种数据格式

一、十进制数转换成浮点数的步骤

1、将十进制数转换成二进制数：整数部分用2来除，小数部分用2来乘；

2、规格化二进制数：改变阶码，使小数点前面仅有第一位有效数字；

3、计算阶码：

◆ 短型浮点数的阶码加上偏移量7FH
◆ 长型浮点数的阶码加上偏移量3FFH
◆ 扩展型浮点数的阶码加上偏移量3FFFH

4、以浮点数据格式存储。

把数值的符号位、阶码和尾数合在一起就得到了该数的浮点存储形式。

注意：尾数是带有一个隐含位的23位数，即：数“1.XXXX”的尾数是“XXXX”，前面的”1”被隐含掉，它只在扩展精度的格式中才被显式表示出来。

例11.1 把十进制数100.25转换成协处理器中的浮点数

解：

1、进制转换：(100.25)10＝(1100100.01)2

2、规格化：(1100100.01)2＝1.10010001×26＝1.10010001×2110

3、计算阶码：110+01111111＝10000101

4、数值的符号位为0，阶码为：10000101，尾数为：1001 0001 0000 0000 0000 000

综合上述可得：(100.25)10的浮点形式为：0 10000101 10010001000000000000000

下面是学习和掌握十进制数转化为浮点数的控件，它可按步骤演示整个转换过程。

几个特殊数据的存储规则：

正0： | 所有的数据位都是0；
负0： | 最高位为1，其它的数据位是0；
正/负无穷： | 符号位为0/1，阶码位全为1，有效数字全为0；
NAN： | 非法的浮点数，阶码位全为1，有效数字不全为0；

其中：NAN — Not-A-Number。


11.1 浮点数转化为十进制数

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点数的数据格式 → 浮点数转换成十进制数

二、浮点数转换成十进制数的步骤

该步骤与前面“十进制数转换成浮点数”的步骤是互逆的，其具体步骤如下：
1、分割数字的符号、阶码和有效数字；
2、将偏移阶码减去偏移，得到真正的阶码；
3、把数字写成规格化的二进制数形式；
4、把规格化的二进制数改变成非规格化的二进制数；
5、把非规格化的二进制数转换成十进制数。

例11.2 把协处理器中的浮点数1100000111001001000000000000转换成十进制数

解

 1、把浮点数1100000111001001000000000000分割成三部分，可得：

符号位是1，阶码是10000011，尾数是1001001000000000000

2、还原阶码：10000011 – 01111111＝100

3、该浮点数的规格化形式：1.1001001×24　　　　(其中前面的“1.”从隐含位而来)

4、该浮点数的非规格化形式：11001.001

5、该浮点数的十进制数为-25.125　　　　　　　　(因为符号位为1，所以，该数是负数)

下面是学习和掌握十进制数转化为浮点数的控件，它可按步骤演示整个转换过程。

三、浮点数说明形式

在汇编语言中，可用DD、DQ和DT来分别说明单精度、双精度和扩展精度的浮点数。

在MASM 6.0系统中，正浮点数前面不能书写‘+’，但MASM 6.11系统更正了这种错误，并提供了新的浮点数说明方法，即：可用REAL4、REAL8和REAL10来分别代替DD、DQ和DT。

在定义浮点数时，要使用伪指令.8087、.287或.387等。

例如：

.387
data1 | DD | 123, -543 | ;定义单精度浮点数
data2 | REAL4 | 3.345E+3 | ;定义单精度浮点数
data3 | REAL8 | 321.545 | ;定义双精度浮点数
data4 | REAL10 | 254.555 | ;定义扩展精度浮点数


11.2 协处理器的结构

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的结构

11.2 协处理器的结构

协处理器，顾名思义，是为与CPU协同工作而设计的，其主要用来提高进行数学和超越函数计算的速度。在80486DX和Pentium处理器中都内置一个与80387完全兼容的协处理器。CPU执行所有的常规指令，协处理器则执行协处理器指令，它们能同时并行地执行各自的指令。由于现在Pentium处理器内部结构的特点，该处理器能同时执行一条协处理器指令和二条整数指令。

11.2.1 协处理器的内部结构

协处理器80x87的内部结构如图11.4所示。它可分为二个主要部分：控制部件(CU)和数值执行部件(NEU)。

图11.4 80x87的内部结构示意图

控制部件(CU)把协处理器接到CPU的系统总线上，协处理器和CPU都监视正在执行的指令流。如果当前将要执行的指令是协处理器指令(即：ESCape指令)，那么，协处理器会自动执行它，否则，该指令将交给CPU来执行。

数值执行部件(NEU)复制执行所有的协处理器指令，它有一个用8个80位的寄存器组成的堆栈，该堆栈用于以扩展精度的浮点数据格式来存放数学指令的操作数和运算结果。在协处理器指令的执行过程中，要么指定该堆栈寄存器中的数据，要么使用压栈/出栈机制来从栈顶存放或读取数据。

在NEU部件中，还有一些记录协处理器工作状态的寄存器，如：状态寄存器、控制寄存器、标记寄存器和异常指针寄存器等。有关这些寄存器的作用将在后面给予分别介绍。


11.2.2 状态寄存器

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的结构 → 状态寄存器

11.2.2 状态寄存器

状态寄存器是用来标识协处理器中指令执行情况的，它相当于CPU中的标志位寄存器。80x87协处理器的状态寄存器如图11.5所示。

15 13 12 11 8 7 0

B

C3

 TOP C2 C1 C0 ES SF PE UE OE ZE DE IE

图11.5　80x87协处理器的状态寄存器示意图

状态寄存器各标志位(或组合位)的含义如下：

◆ B(Busy，忙)

忙标志位用来表明协处理器是否正在执行协处理器指令，它可用FWAIT指令来测试。在80287及其以后的协处理器中，协处理器和CPU能自动实现同步，所以，现在在运行任务时，无须测试忙标志。

◆ C3~C0(条件编码位)

四位条件编码位的组合含义如表11.2所列。

表11.2　状态寄存器中条件编码位的组合含义

指 令 | C3 | C2 | C1 | C0 | 功 能
FTST、FCOM | 0 | 0 | X | 0 | ST＞操作数或(0 FTST)
 | 0 | 0 | X | 1 | ST＜操作数或(0 FTST)
 | 1 | 0 | X | 0 | ST＝操作数或(0 FTST)
 | 1 | 1 | X | 1 | ST不可比较
FPREM | Q1 | 0 | Q0 | Q2 | Q2Q1Q0是商的右边3位
 | ? | 1 | ? | ? | 未完成
FXAM | 0 | 0 | 0 | 0 | +unnormal
 | 0 | 0 | 0 | 1 | +NAN
 | 0 | 0 | 1 | 0 | -unnormal
 | 0 | 0 | 1 | 1 | -NAN
 | 0 | 1 | 0 | 0 | +normal
 | 0 | 1 | 0 | 1 | +∞
 | 0 | 1 | 1 | 0 | -unnormal
 | 0 | 1 | 1 | 1 | -∞
 | 1 | 0 | 0 | 0 | +0
 | 1 | 0 | 0 | 1 | 空
 | 1 | 0 | 1 | 0 | -0
 | 1 | 0 | 1 | 1 | 空
 | 1 | 1 | 0 | 0 | +denormal
 | 1 | 1 | 0 | 1 | 空
 | 1 | 1 | 1 | 0 | -denormal
 | 1 | 1 | 1 | 1 | 空

其中，normal—标准的浮点数，unnormal—有效数字前面是0，如：0.XXXX，denormal—阶码是最大的负值，NAN—见11.1.3节中几个特殊数据的说明。

◆ TOP(栈顶)

该三位二进制000~111用来表明当前作为栈顶的寄存器，通常其值为000。

◆ ES(错误汇总)

ES＝PE+UE+OE+ZE+DE+IE(逻辑或运算)，在8087协处理器中，当ES为1时，将发出一个协处理器中断请求，但在其后的协处理器中，不再产生这样的协处理器中断申请。

◆ SF(堆栈溢出错误)

该状态位用来表明协处理器内部的堆栈是否有上溢或下溢错误。

◆ PE(精度错误)

该状态位用来表明运算结果或操作数是否超过先前设定的精度。

◆ UE(下溢错误)

该状态位用来表明一个非0的结果太小，不能用控制字节所选定的当前精度来表示。

◆ OE(上溢错误)

该状态位用来表明一个非0的结果太大，不能用控制字节所选定的当前精度来表示，即超过了当前精度所能表示的数据范围。

如果在控制寄存器中屏蔽该错误标志，即设控制寄存器中的OM为1，那么，协处理器把上溢结果定义为无穷大。

◆ ZE(除法错误)

该状态位用来表明当前执行了“0作除数”的除法运算。

◆ DE(非规格化错误)

该状态位用来表明当前参与运算的操作数中至少有一个操作数是没有规格化的。

◆ IE(非法错误)

该状态位用来表明执行了一个错误的操作，如：求负数的平方根，也可用来表明堆栈的溢出错误、不确定的格式(0/0，∞，-∞等)错误，或用NAN作为操作数。


11.2.2 存取状态寄存器

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的结构 → 存取状态寄存器

对于协处理器中状态寄存器的内容，程序员可用指令FSTSW把其值送到内存单元中。如果当前使用的是80287及其以后的协处理器，那么，可用指令“FSTSW AX”把该状态寄存器的值传送给通用寄存器AX。一旦状态寄存器的值复制到内存或AX中，那么，就可对其各位进行分析，并可检测出当前协处理器的工作状态。

对于80287协处理器，它还可通过I/O地址00FAH～00FFH来实现其与CPU之间的数据交换，而80387~Pentium系列芯片，则是通过I/O地址800000FAH～800000FFH来实现这两者之间的数据交换。

当状态寄存器的内容传给AX之后，一般可用下面二种方法来检测协处理器的状态。

方法1：用TEST指令来检测其相应的状态位。

例11.3 检测是否有“0作除数”的错误。

FDIV | DATA1 | ;用协处理器中堆顶数据去除DATA1
FSTSW | AX | ;把状态寄存器的值传送给AX
TEST | AX, 4 | ;测试第2位，即：检测ZE是否为1
JNZ | DIV_ERR

例11.4 检测是否有“非法操作数”的错误。

FSQRT |  | ;求协处理器中堆顶数据的平方根
FSTSW | AX
TEST | AX, 1 | ;测试第0位，即：检测IE是否为1
JNZ | SQRT_ERR

方法2：用SAHF指令把AX的低字节传送给CPU的标志位寄存器，然后再用条件转移指令来完成相应的检测。

例11.5 检测内存单元的数据与协处理器堆顶数据之间的大小关系。

FCOM | DATA1 | ;内存单元DATA1的值与协处理器堆顶数据进行比较
FSTSW | AX
SAHF |  | ;把AX的低字节存入CPU的状态寄存器
JE | ST_EQUAL | ;具体大小关系的决定可见表11.2中的“FCOM”
JB | ST_BELOW
JA | ST_ABOVE


11.2 协处理器的结构

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的结构 → 控制寄存器

11.2.3 控制寄存器

控制寄存器主要用于浮点数精度选择的控制、四舍五入的控制和无穷大的控制等，其低6位还可用来决定是否屏蔽协处理器的异常。指令FLDCW可用来设置控制寄存器的值。控制寄存器中控制位的分布如图11.6所示，其控制位的含义如表11.3所列。

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

IC

RC

 PC 　 　 PM UM OM ZM DM IM

图11.6 控制寄存器的控制位分布示意图

表11.3 控制寄存器中控制位的含义

控制位 | 控制功能说明
IC(无穷大控制) | 0—投影，假定是无符号无穷； 1—仿射，允许正、负无穷
RC(舍入控制) | 00—最接近或偶数，01—舍入成负无穷， 11—舍入成负无穷，10—截成0
PC(精度控制) | 00—单精度，01—保留，11—双精度，10—扩展精度
PM | 精度错误屏蔽位 | 若屏蔽位的值为1，则状态寄存器的相应位被屏蔽。
UM | 下溢出屏蔽位
OM | 上溢出屏蔽位
ZM | 除数为0屏蔽位
DM | 非规格化操作数屏蔽位
IM | 非法操作屏蔽位

11.2.4 标记寄存器

标记寄存器用来表明协处理器堆栈中各存储单元内容的状态，也就是说，该寄存器可表明堆栈中的数据是合法的，还是非法的，是无穷，还是0或空等。该标记寄存器的结构如图11.7所示。

15 13 11 9 7 5 3

1　0

TAG(7) TAG(6) TAG(5) TAG(4) TAG(3) TAG(2) TAG(1) TAG(0)

图11.7 标记寄存器结构示意图

其中：TAG(i)的取值含义：00—合法，01—0，10—非法或无穷，11—空

在协处理器中，查看标记寄存器的方法是使用指令FSTENV、FSAVE或FRSTOR，它们都能使标记寄存器与其它协处理器数据一起转存。


11.3 协处理器的指令系统

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的指令系统

11.3 协处理器的指令系统

协处理器共有68条不同的指令，汇编程序在遇到协处理器指令助忆时，都会将其转换成机器语言的ESC指令，ESC指令代表了协处理器的操作码。

在协处理器指令在执行过程中，需要访问内存单元时，CPU会为其形成内存地址。协处理器在协处理器指令期间内利用数据总线来传递数据。80287协处理器利用I/O地址00FAH～00FFH来实现其与CPU之间的数据交换，而80387~Pentium系列芯片，则是利用I/O地址800000FAH～800000FFH来实现这两者之间的数据交换。

11.3.1 指令操作符的命名规则

协处理器指令的操作符(或助忆符)在命名设计时，遵循了下列规则：

1、在操作符后面加上字母P：表示该指令执行完后，还进行一次堆栈弹出操作。如：FADD和FADDP等；

2、在操作符后面加上字母R：表示该操作是反模式，它仅限于减法、除法指令。如：FSUB和FSUBR等；

正模式 —— 栈顶数据＝栈顶数据 op 指令操作数，或OPN1＝OPN1 – OPN2
反模式 —— 栈顶数据＝指令操作数 op 栈顶数据，或OPN1＝OPN2 – OPN1

假设：栈顶数据为10，内存变量data的值为1，分别执行下列指令将有不同的结果。

FSUB | data | ;指令执行后，栈顶数据为9
FSUBR | data | ;指令执行后，栈顶数据为-9
FSUB | ST, ST(1) | ;指令执行后，ST=ST-ST(1)
FSUBR | ST, ST(1) | ;指令执行后，ST=ST(1)-ST

3、操作符的第2个字母是I：表示内存中数据是整数。它对加、减、乘除指令都有效。

例如：FADD　data——浮点数加法；

FIADD　data——整数加法，它表示内存单元data是一个整数，把该整数加到栈顶的浮点数上。

4、操作符的第2个字母是N：表示在指令执行之前检查非屏蔽数值性错误。如：FSAVE和FNSAVE等，前者称为等待形式(wait version)，后者称为非等待形式(no-wait version)。

在使用.8087伪指令情况下，汇编程序会在等待形式的指令前面加上指令WAIT，而在非等待形式的指令前面加上空操作指令NOP。

理解了上述操作符命名规则，就能很容易地区分同类指令之间的差异。


11.3.2 数据传送类指令

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点运算指令 → 浮点数传送指令

11.3.2 数据传送指令

为了满足协处理器和CPU之间进行数据交流的需求，就需要实现内存单元和协处理器之间进行数据传送的指令。协处理器的指令系统中有三大类数据传送指令：BCD传送指令、浮点数传送和整数传送指令。

一、BCD传送指令

1、FBLD

指令格式：FBLD　MemBCD(*)
指令功能：将内存中的BCD数据压入协处理器的堆栈中；

(*) MemType是指定数据类型Type的内存单元，如：MemBCD是BCD类型的存储单元。此后不再说明。

2、FBSTP

指令格式：FBSTP　MemBCD
指令功能：将协处理器中的BCD数据存入内存，并进行堆栈的弹出操作。

例如：

.387
data1 | DT 123, -543
data2 | DT 2.5
……
FBLD | data1 | ;把BCD数据123压进栈
FBSTP | data2 | ;把当前堆顶数据弹出，并传送给BCD型的内参单元

二、浮点数传送指令

1、FLD
 | 指令格式： | FLD STReg(*)/MemReal
 | 指令功能： | 将浮点数据压入协处理器的堆栈中。当进行内存单元内容压栈时，系统会自动决定传送数据的精度。比如：用DD或REAL4定义的内存单元数值是单精度数等。

(*) STReg是协处理器堆栈寄存器ST(0)~ST(7)。

例如：

.387
data1 | DD 123, -543
data2 | REAL8 -321.5
data3 | REAL10 2.5
……
FLD | data1 | ;压一个单精度数据进栈
FLD | data2 | ;压一个双精度数据进栈
FLD | ST(0) | ;把堆栈寄存器ST(0)的值再压进栈
FLD | data3 | ;压一个扩展精度数据进栈

2、FST
指令格式： | FST STReg/MemReal
指令功能： | 将协处理器堆栈栈顶的数据传送到目标操作数中。在进行数据传送时，系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成相应精度的数据。

3、FSTP

指令格式： | FSTP STReg/MemReal
指令功能： | 与FST相类似，所不同的是：指令FST执行完后，不进行堆栈的弹出操作，即：堆栈不发生变化，而指令FSTP执行完后，则需要进行堆栈的弹出操作，堆栈将发生变化。请见11.3.1节中的指令操作符命名规则的说明。

4、FXCH

指令格式： | FXCH [STReg]
指令功能： | 将指定的寄存器中的浮点数与堆顶浮点数进行交换。如果不指定操作数，那么，默认ST和ST(1)二者之间交换数据。

例如：FXCH ST(2)——栈顶数据与堆栈寄存器ST(2)进行数据交换。

三、整数传送指令

1、FILD

指令格式：FILD　MemInt

其中：MemInt是定义为整型数据类型的内存单元，但不能是用DB定义的存储单元。下同，不再叙述。

2、FIST/FISTP

指令格式： | FIST MemInt FISTP MemInt
其中： | Mem是定义整型数据类型的内存单元，但不能是用DB定义的存储单元。
指令功能： | 将协处理器堆栈栈顶的数据传送到目标存储单元中。在进行数据传送时，系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成整型数据。

指令FIST和FISTP的区别在于堆栈操作，详细请见11.3.1中的命名规则说明。


11.3.3 数学运算类指令

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点运算指令 → 数学运算指令

11.3.3 数学运算指令

在协处理器的指令系统中，有关数学运算指令有：加法指令、减法指令、乘法指令、除法指令和求平方根指令等。涉及数学运算的指令有比例运算、舍入运算、求绝对值运算和改变数值符号运算等指令。

1、加法指令

指令格式： | FADD | [STReg1, STReg2](*)
 | FADD | MemReal
 | FADDP | STReg, ST
 | FIADD | MemInt

(*) 在此指令格式下，如果同时指定了二个堆栈寄存器，那么，其中一个寄存器必须是ST。其它指令的同类格式与此同理。

指令FADD含有二个隐含操作数ST(1)和ST，其运算功能是：从堆栈中弹出这二个操作数，然后把计算的“和”压入堆栈，即：ST＝ST(1)+ST。

指令“FADD MemReal”的功能：ST＝ST+MemReal
指令“FADDP STReg, ST”的功能：STReg＝STReg+ST，并弹出堆栈的栈顶
指令“FIADD MemInt”的功能：ST＝ST+MemInt

2、减法指令

指令格式： | FSUB | [STReg1, STReg2]
 | FSUB | MemReal
 | FSUBP | STReg, ST
 | FISUB | MemInt
 | FSUBR | [STReg1, STReg2] | ;后四条指令是前四条指令的反模式形式
 | FSUBR | MemReal
 | FSUBRP | STReg,ST
 | FISUBR | MemInt

指令FSUB含有二个隐含操作数ST(1)和ST，其运算功能是：从堆栈中弹出这二个操作数，然后把计算的“差”压入堆栈，即：ST＝ST(1)-ST。

指令“FSUB MemReal”的功能：ST＝ST-MemReal
指令“FSUBP STReg, ST”的功能：STReg＝STReg-ST，并弹出堆栈的栈顶
指令“FISUB MemInt”的功能：ST＝ST-MemInt

反模式的四条指令的功能在此从略，请参阅11.3.1中的有关说明。

3、乘法指令

指令格式： | FMUL | [STReg1, STReg2]
 | FMUL | MemReal
 | FMULP | STReg, ST
 | FIMUL | MemInt

4、除法指令

指令格式： | FDIV | [STReg1, STReg2]
 | FDIV | MemReal
 | FDIVP | STReg, ST
 | FIDIV | MemInt
 | FDIVR | [STReg1, STReg2] | ;后四条指令是前四条指令的反模式形式
 | FDIVR | MemReal
 | FDIVRP | STReg, ST
 | FIDIVR | MemInt

例如：

.387
word1 | DW 20
data1 | REAL8 8
data2 | REAL8 -2
data3 | REAL8 -12
……
FLD | data1 | ;本例只是显示指令的使用方法，无具体的实际功能
FLD | data2
FLD | data3
FDIV | ST(2), ST
FDIV | data1
FDIVP | ST(2), ST
FIDIV | word1

5、其它数学运算指令

在协处理器中，除了完成具体的数学运算指令外，还设置了若干个与数学运算有关的运算指令。具体的运算指令及其功能描述如表11.4所列。

表11.4 与数学运算有关的其它指令

指令格式 | 指令的功能
FSQRT | 求栈顶数据的平方根。如果对负数求其平方根，则会发生错误，并可通过检测状态寄存器的标志位IE来确定。
FSCALE | 将ST(1)中的数(转换成整数)加上ST的阶码，该指令能快速完成乘/除2n的运算。ST(1)中的数必须在2-15到215之间。
FPREM/FPREM1 | ST＝ST%ST(1)，80387及其以后的协处理器支持FPREM1。
FRNDINT | 对栈顶数据进行舍入运算，使之转换成整数。
FXTRACT | 将栈顶数据分成二部分：无偏阶码和尾数。尾数存入栈顶，无偏阶码存入ST(1)。它常用将浮点数转换成小数形式打印输出。
FABS | ST＝|ST|，即：求栈顶数据的绝对值。
FCHS | ST＝-ST，即：改变栈顶数据的符号。


11.3.4 比较类指令等

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点运算指令 → 比较运算指令

11.3.4 比较运算指令

使用比较指令是将栈顶中的数与其它操作数进行比较，比较结果存于状态寄存器的条件编码位C3~C0处(参阅表11.2)。具体的比较运算指令及其功能描述如表11.5所列。

表11.5 比较运算指令及其功能

指令格式 | 指令的功能
FCOM | 将栈顶数据与另一个操作数进行比较，该操作数可以存储在堆栈寄存器中，也可存储在内存中。 当在指令中不指定操作数时，其默认的操作数是ST和ST(1)。
FCOMP/FCOMPP | 此指令的比较功能与FCOM相一致，所不同的是指出从堆栈中弹出一个，还是二个数据。
FICOM MemInt FICOMP MemInt | 栈顶数据与内存单元进行整数比较。指令FICOMP还要弹出栈顶数据。
FUCOM [STReg] FUCOMP [STReg] FUCOMPP | 反向比较，其功能与上面同类指令类似。
FTST | 栈顶数据与0进行比较。比较结果对条件编码位的影响如表11.2所示。
FXAM | 检测栈顶数据是正数、负数，还是规格化数。比较结果对条件编码位的影响如表11.2所示。

11.3.5 超越函数运算指令

超越函数运算指令是用来实现三角函数或一些特殊的算术表达式而设置的，具体的指令及其功能说明如表11.6所列。

表11.6 超越函数运算指令及其功能

指令格式 | 指令的功能
FPTAN | 求部分Y/X=tanθ，角度θ存于栈顶，其结果： ST=X，ST(1)=Y (1)、在8087-80287中，角度θ的范围上是0~π/4； (2)、在80387以后的协处理器中，角度θ的范围上是0~263； 如果交度超过其取值范围，则状态IE标定为非法错误。
FPATAN | 求部分反正切值θ=arctan(X/Y)，X取自ST，Y取自ST(1)； 其中：X和Y必须满足：0≤Y＜X＜∞； 指令执行时进行一次弹出操作，结果存入栈顶。
F2XM1 | 求2X-1的值，X取自栈顶，结果也存放栈顶，X的取值范围：-1~1。
FCOS或FSIN | 求栈顶ST中角度的正弦或余弦值，ST中存放的是弧度。 运算结果也存于ST。
FSINCOS | 求栈顶ST中角度的正弦和余弦值，ST中存放的是弧度。 运算结果：ST=正弦值，ST(1)=余弦值。
FYL2X | 计算Ylog2X的值，X=ST，Y=ST(1)，其中：X＞0。 结果存入有一个弹出操作后的栈顶。
FYL2XP1 | 计算Ylog2(X+1)的值，X=ST，Y=ST(1)，其中：0＜X＜1-2-0.5。 结果存入有一个弹出操作后的栈顶。


11.3.6 常数操作指令等

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 浮点运算指令 → 常数操作指令

11.3.6 常数操作指令

为了计算的方便，协处理器提供了几个将常用常数压栈的指令。如表11.7所示。

表11.7 常数压栈指令及其常数值

指令格式 | 指令的功能 | 指令格式 | 指令的功能
FLDZ | ST=+0.0 | FLDL2T | ST= log210
FLD1 | ST=+1.0 | FLDLG2 | ST= log102
FLDPI | ST=π | FLDLN2 | ST= loge2
FLDL2E | ST=log2e |  |

11.3.7 协处理器控制指令

协处理器控制指令是用来实现控制协处理器状态而设置的，它包括协处理器的初始化、状态寄存器内容的存取、异常处理和任务切换等操作。具体的指令及其功能说明如表11.8所列。

表11.8 比较运算指令及其功能

指令格式 | 指令的功能
FINIT/FNINIT | 初始化协处理器，初始化后协处理器的状态如表11.9所列。
FLDCW Mem16 | 将由操作数指定的字存储单元内容存储到控制寄存器中。
FSTCW Mem16 FNSTCW Mem16 | 把控制寄存器的内容存储到由操作数指定的字存储单元。与指令“FLDCW”相对应。
FSTSW Mem16 FSTSW AX FNSTSW Mem16 FNSTSW AX | 将控制寄存器的内容传送到寄存器AX中。 在8087协处理器中无此指令。
FCLEX/FNCLEX | 清除状态寄存器中的“错误”和“忙”标志。
FSAVE Mem FNSAVE Mem | 将全部机器状态存储到内存中。
FRSTOR Mem | 从内存复原机器状态，它可恢复由指令“FSAVE/FNSAVE”保存的信息。
FSTENV Mem FNSTENVMem | 存储协处理器环境。
FLDENV Mem | 重新装入由指令FSTENV/FNSTENV存储的协处理器环境
FINCST | 堆栈指针加1。
FDECSTP | 堆栈指针减1。
FFREE ST(i) | 释放堆栈寄存器，即使其标记为空，但其内容并没有改变。
FNOP | 协处理器的空操作
FWAIT | 使处理器处于等待状态，以便协处理器完成其操作。该指令主要用于在CPU访问被协处理器影响的内存数据之前。

表11.9 协处理器初始化的状态

控制项 | 值 | 状态含义
控制位IC | 0 | 投影
控制位PC | 10 | 扩展精度
控制位RC | 00 | 最近舍入或偶数
错误屏蔽 | 11111 | 错误位关闭
忙标志 | 0 | 不忙
C3~C0 | ???? | 未定
TOP | 000 | 堆栈栈顶设定为寄存器0
ES | 0 | 无错误
错误位 | 00000 | 无错误
全部标记 | 11 | 空
寄存器 | -- | 不改变


11.4 协处理器的编程举例

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 协处理器的编程举例

11.4 协处理器的编程举例

本节提供几个利用协处理器进行编程的例子，从这些例子可看出使用协处理器指令编程的方法和技巧。

例11.6 假设圆的半径存于数组RAD中，计算出每个圆的面积，并存于数组AREA中。

解：

例11.7 已知L=0.0001，C=0.002，试计算下列公式的值，并存于单精度数F中。

解：

例11.8 已知L=4.0，F按每次递增10.0的幅度从10.0增加到1000.0，试按公式Y＝2πFL，计算出100个Y值，并把它们存入数组RES中。

解：显示解答

例11.9 把内存单元DATA中存放的单精度浮点数以小数的形式显示在屏幕上。

解：显示解答

例11.10 从键盘上读入一个带小数的数字字符串，然后把它转换成单精度浮点数，并存入内存单元DATA中。

解：显示解答

例11.11 编写一个子程序，求解一元二次方程的根ax2 + bx + c = 0的根。

解：显示解答


11.5 习题

1 有符号整数
1 有符号整数
2 BCD码数据
2 BCD码数据
3 浮点数
3 浮点数
1 操作符的命名规则
1 操作符的命名规则
2 数据传送指令
2 数据传送指令
3 数学运算指令
3 数学运算指令
4 比较运算指令
4 比较运算指令
5 超越函数运算指令
5 超越函数运算指令
6 常数操作指令
6 常数操作指令
7 协处理器控制指令
7 协处理器控制指令
本章的学习内容和要求
本章的学习内容和要求
11.1 协处理器的数据格式
11.1 协处理器的数据格式
11.2 协处理器的结构
11.2 协处理器的结构
11.3 协处理器的指令系统
11.3 协处理器的指令系统
11.4 协处理器的编程举例
11.4 协处理器的编程举例
11.5 习题
11.5 习题

→ 习题

11.5 习题

11.1、简述协处理器可以处理的三种整型数据格式、三种浮点数据格式及其表示范围。
11.2、简述BCD码数据在内存中所占的位数和存储的形式。
11.3、在MASM V6.11中，如何定义三种不同精度的浮点数？试举例说明。
11.4、写出满足下列要求的变量定义语句。
1)、存储双精度数23.44到内存FROG处； 2)、存储32位有符号整数-123到内存Data2处； 3)、存储单精度数-23.8到内存Data3处。
11.5、什么是+0、-0和NAN?
11.6、将下列各数转换成单精度浮点数。
1)、28.75 3)、–0.615 5)、-1000.5 | 2)、624 4)、+0.0 6)、89.6
11.7、将下列单精度浮点数转换成十进制数。
1)、11000000 11110000 00000000 00000000 2)、00111111 00010000 00000000 00000000 3)、01000011 10011001 00000000 00000000 4)、01000000 00000000 00000000 00000000 5)、01000001 00100000 00000000 00000000
11.8、状态寄存器中，C3~C0的作用是什么？
11.9、状态寄存器中，IE位的作用是什么？
11.10、在80X87中如何选择舍入模式?
11.11、协处理器指令的一般命名规则有哪些？
11.12、哪条协处理器指令用到微处理器的AX寄存器？
11.13、协处理器复位时，栈顶寄存器是 ？
11.14、简述下列协处理器指令的功能。
1)、FSTSW AX 2)、FINIT 3)、FADD 4)、FSTP DATA 5)、FST DATA | 6)、F2XM1 7)、FFREE ST(2) 8)、FSAVE 9)、FILD Data 10)、FCHS
11.15、“除”和“反向除”有何不同？
11.16、简述FTST和FXAM指令之间的差异。
11.17、简述FSTSW和FNSTSW指令之间的差异。
11.18、FSQRT指令执行后，必须测试处理器状态寄存器的哪个标志位？
11.19、何时在程序中使用指令FWAIT？
11.20、分别选用一条协处理器指令，完成下列功能。
1)、将π压入栈顶 2)、将1.0送入栈顶 3)、将栈顶中的数加上寄存器3中的数 4)、将栈顶中的数减去寄存器2中的数，结果存入寄存器2中
11.21、何时在程序中使用指令FWAIT？
11.22、对下列每一种功能要求，编写一个汇编语言程序实现之(假设未注明的变量都是单精度浮点类型)。
1)、Delta = sqrt(b2 – 4ac) 2)、EBX＝π×EBX ;EBX是32位寄存器 3)、E = (a+b)/(c-d) 4)、C=1/(27×л×F) 5)、把2~10各整数的平方根依次存入单精度浮点数组ROOTS
6)、假设有三个都含有100个元素的双精度浮点数数组D1、D2和D3，试依次计算：D3(i)=D2(i)×D1(i)，i=1, 2, ……, 100
11.23、根据公式Log10X＝Log2X／Log210，编写子程序LOGl0，它用以求栈顶以10为底的对数值，结果仍存入栈顶。 再按公式D3(i)＝20×Log10(D2(i)/D1(i))计算20组数据，其中：D1、D2和D3均是含有20个元素的单精度浮点数数组。


第12章 汇编语言和C语言

本章的学习内容和要求
本章的学习内容和要求
12.1 汇编语言的嵌入
12.1 汇编语言的嵌入
12.2 C语言程序的汇编输出
12.2 C语言程序的汇编输出
12.3 一个具体的例子
12.3 一个具体的例子
12.4 习题
12.4 习题

第12章 汇编语言和C语言

C/C++语言是一个被广泛使用的程序设计语言，它不仅具有良好的高级语言特征，而且还具有一些低级语言的特点，如：寄存器变量、位操作等。所以，C语言的程序与汇编语言程序之间能很平滑地衔接。另外，目前主要的C语言程序开发环境，如：Turbo C/C++、Borland C/C++等，也都提供了很好的混合编程手段。

本章主要介绍汇编语言和C语言的混合编程和调用方法。虽然其它高级语言，如：Pascal、Basic等，也可与汇编语言混合使用，但出于其应用范围的考虑，不再对它们进行介绍，感兴趣的读者可参阅有关技术资料。

12.1 汇编指令的嵌入

为了提高C语言程序内某特殊功能段的处理效率，我们可以在其源程序中嵌入一段汇编语言程序段。这样做，虽然能达到提高了程序处理效率的目的，但它无疑以丧失源程序的可移植性为代价。所以，当想用C语言和汇编语言混合编程时，程序员需要权衡采用这种方法的利与弊。

在C语言中，嵌入汇编语言的语法如下(*)：

asm <opcode> <operands> <; or newline>

注意：这里的分号';'不是汇编语言中起注释作用的分号，而是作为语句的分隔符。

若C语言源程序中嵌入一条汇编语句，则可按下列方式来做：

asm mov ax, data

若要嵌入一组汇编语句，则需要用括号'{'和'}'把它们括起来。

asm {
     mov　ax, data1
     xchg　ax, data2
     mov　data1, ax　　　　　//实现整型变量data1和data2之值的交换
}

例12.1 在C语言源程序中嵌入汇编语言语句实现赋值语句A=A+B+C，其中：A、B、C都是整型变量。

解： ……

asm {push ax　　　　　　//实现整型变量A=A+B+C
mov　ax, A
add　ax, B
add　ax, C
mov　A, ax
pop　ax
}


12.2 C语言源程序的汇编输出

本章的学习内容和要求
本章的学习内容和要求
12.1 汇编语言的嵌入
12.1 汇编语言的嵌入
12.2 C语言程序的汇编输出
12.2 C语言程序的汇编输出
12.3 一个具体的例子
12.3 一个具体的例子
12.4 习题
12.4 习题

→ C语言程序的汇编输出

12.2 C语言程序的汇编输出

在Turbo C++或Borland C++编程环境下，我们可TCC或BCC行命令把一个C语言的源程序转换成汇编语言的源程序。通过阅读汇编语言程序可以很准确地知道C语言语句的功能是如何实现的，这样，可为将来学习《编译原理》课程中的"寄存器调度"和"代码生成"等相关知识打下良好的基础。

C语言源程序转换的命令格式如下：

TCC -S t1.cpp 或 BCC -S t1.cpp　　　;假设其文件名为t1.cpp

若命令TCC/BCC不带参数的话，则将显示其使用方法。

下面是C语言程序及其相对应的汇编语言程序，希望读者能逐行对照理解它们语句之间的转换关系，这将能进一步理解高级语言的语句功能。

1、C语言程序清单

#include <stdio.h>
int sum(int a, int b, int c)
  {
   return (a+b+c);
  }
void main()
{int a, b, c;
a = b = 12;
c = 32;
printf("%d", sum(a,b,c));
}

2、生成的汇编语言程序清单

……　　　　　　　　　　　　　　　　　　　　　 ;一系列辅助说明信息
_TEXT　segment byte public 'CODE'　　　　　　;代码段的开始
;int sum(int a, int b, int c)　　　　　　　　　　　;C语言语句
            assume cs : _TEXT
@sum$qiii　proc　 near　　　　　　　　　　　 ;过程说明，对应于C语言sum过程
            push　bp　　　　　　　　　　　　　;为读取堆栈中的参数作准备，可参见第7.3.3节内容
            mov　bp, sp
;{
    ;return(a+b+c);
            mov　ax, word ptr [bp+4]
            add　ax, word ptr [bp+6]
            add　ax, word ptr [bp+8]
            jmp　short @1@58
@1@58:
;}
            pop　bp　　　　　　　　　　　　　　;sum子程序结束的代码
            ret
@sum$qiii　endp
;void main()
            assume cs : _TEXT
_main　　　proc　 near　　　　 　　　　　　　 ;过程说明，对应于C语言中的主函数main()
            push　bp
            mov　bp, sp
            sub　sp, 6
;{int a, b, c;　　　　　　　　　　　　　　　　　　;局部变量是用堆栈来存储的，请见第7.5.10节
; a = b = 12;　　　　　　　　　　　　　　　　　　;给局部变量赋值
            mov　ax, 12　　　　　　　　　　　　;用给堆栈单元赋值来实现对局部变量的赋值
            mov　word ptr [bp-4], ax
            mov　word ptr [bp-2], ax
; c = 32;
            mov　word ptr [bp-6], 32
; printf("%d", sum(a,b,c));　　　　　　　　　　　　;调用系统标准函数
            push　word ptr [bp-6]
            push　word ptr [bp-4]
            push　word ptr [bp-2]
            call　near ptr @sum$qiii　　　　　　;用汇编语言形式调用自定义函数sum
            add　sp, 6
            push　ax
            mov　ax, offset DGROUP : s@
            push　ax
            call　near ptr _printf　　　　　　　　;用汇编语言调用标准函数printf
            pop　cx
            pop　cx
; }
            mov　 sp, bp　　　　　　　　　　　 ;main子程序结束的代码
            pop　 bp
            ret
_main　　　endp
_TEXT　　　ends　　　　　　　　　　　　　　　　;代码段的结束
_DATA　　 segment　word　public　'DATA'　　　 ;数据段的定义
            s@　label　byte
                 db　'%d'
                 db　0
_DATA　　　ends

public　 _main　　　　　　　　　　 ;下面说明函数的属性，请见第7.6.3节
public　 @sum$qiii
extrn　 _printf : near
_s@　 equ　s@
end


12.3 简单的屏幕编辑程序

本章的学习内容和要求
本章的学习内容和要求
12.1 汇编语言的嵌入
12.1 汇编语言的嵌入
12.2 C语言程序的汇编输出
12.2 C语言程序的汇编输出
12.3 一个具体的例子
12.3 一个具体的例子
12.4 习题
12.4 习题

→ 简单的屏幕编辑程序

12.3 简单的屏幕编辑程序

下面是一个简单的屏幕编辑的C语言程序，它不仅涉及到键盘处理、光标定位、屏幕输出、字符颜色等，而且还运用了C语言和汇编语言的混合编程方法。若读者能把它改写成相同功能的汇编语言程序，那么，可以说，你已基本掌握了中断的使用方法，也对计算机输入输出的工作方式有了更进一步的认识。

该程序的功能：
◆ 可用移动光标键↑、↓、←和→移动光标1行或1列，也可用TAB/Shift+TAB、Home和End键跳跃地移动光标；
◆ 当光标已在第1行，再向上移动时，这时，光标被定位到第25行，反之也然；
◆ 当光标已在第0列，还要向左移动时，光标被定位到第79列，反之也然；
◆ 当按下^W或^Z时，屏幕将向上或向下滚动1行；
◆ 显示当前键盘的状态：大小写状态、数字键盘状态和插入/修改状态；
◆ 如果按普通的键，将在屏幕上显示该字符，如果按下用Alt、Ctrl或Shift组合的组合键，则显示该按键的扫描码；
◆ 用Esc键来结束程序的运行。

C语言的源程序清单：

#define　NUM_KEY　0x20　　　　　　/*　键盘状态字宏定义　*/
#define　CAPS_KEY　0x40
#define　ESCAPE　27　　　　　　　　/*　几个功能键的宏定义　*/
#define　TAB_KEY　9
#define　SHIFT_TAB　15
#define　CTRL_W　23
#define　CTRL_Z　26
#define　UP_ARROW　72
#define　DOWN_ARROW　80
#define　LEFT_ARROW　75
#define　RIGHT_ARROW　77
#define　INSERT　82
#define　END_KEY　79
#define　HOME_KEY　71
#define　UP_SCROLL　6　　　　　　/*　屏幕滚动宏定义　*/
#define　DOWN_SCROLL　7

#include <dos.h>
int insert, cap_key, num_key;

/* up_down：屏幕滚动方式：6-向上滚; 7-向下滚
  (l_row, l_col)-(r_row, r_col)：滚动矩形的对角线坐标
  num：屏幕滚动的行数，0-清屏
  attr：滚动后所剩下行的属性　*/
cls(int up_down, int l_row, int l_col, int r_row, int r_col, int num, int attr)

{union REGS in, out;

in.h.ah = up_down;　in.h.al = num;
in.h.ch = l_row;　　　in.h.cl = l_col;
in.h.dh = r_row;　　　in.h.dl = r_col;
in.h.bh = attr;
int86(0x10, &in, &out);
}

    get_cursor(int *x, int *y)　　/* 取当前光标的位置，并分别存入变量x和y中 */

{union REGS in, out;

in.h.ah = 3;　in.h.bh = 0;
int86(0x10, &in, &out);
*x = out.h.dh;　*y = out.h.dl;
}

    locate(int row, int col)　　/* 把光标设置在(row, col)位置 */

{union REGS in, out;

in.h.ah = 2;　　in.h.bh = 0;
in.h.dh = row;　in.h.dl = col;
int86(0x10, &in, &out);
}

    disp_string(int row, int col, char string[])　　/* 在(row, col)位置显示字符串string */

{struct REGPACK in, out;
int x, y;

get_cursor(&x, &y);
locate(row, col);
in.r_ds = FP_SEG(string);　in.r_dx = FP_OFF(string);　in.r_ax = 0x900;　intr(0x21, &in);
locate(x, y);
}

    check_key_state()　　/* 在(row, col)位置以属性attr显示字符ch */

{char state;

state = bioskey(2);
if (state & CAPS_KEY)
   {if (!cap_key) {cap_key = 1; disp_string(24, 66, "CAP$");}
   }
else if (cap_key) {cap_key = 0; disp_string(24, 66, " $");}
if (state & NUM_KEY)
  {if (!num_key) {num_key = 1; disp_string(24, 70, "NUM$");}
  }
else if (num_key) {num_key = 0; disp_string(24, 70, " $");}
}

    insert_key()　　/* 在最后一行显示插入/修改状态标志，并改变光标形状 */

{union REGS in, out;

insert = 1 - insert;
disp_string(24, 74, (insert ? "INS$" : " $"));　　/* 显示插入/修改标志 */
in.h.ah = 1;
in.h.ch = (insert ? 0 : 14); in.h.cl = 15;　　　　/* 改变光标的形状 */
int86(0x10, &in, &out);
}

    move_right(int row, int col, int len)　　/* 在(row, col)位置之后的字符和属性向后移len个位置 */

{int j, attr;
char ch;

for (j = 79; j >= col+len; j--)
    {read_char_attr(row, j-len, &ch, &attr);
     write_char_attr(row, j, ch, attr);
    }
}

    read_char_attr(int row, int col, char *ch, int *attr)　　/* 在读(row, col)位置字符和属性，并分别存入ch和attr */

{union REGS in, out;

locate(i, j);
in.h.ah = 8; in.h.bh = 0;
int86(0x10, &in, &out);
*ch = out.h.al; *attr = out.h.ah;
}

    write_char_attr(int row, int col, char ch, int attr)　　/* 在(row, col)位置以属性attr显示字符ch */

{union REGS in, out;

locate(row, col);
in.h.ah = 9; in.h.al = ch;
in.h.bh = 0; in.h.bl = attr; in.x.cx = 1;
int86(0x10, &in, &out);
}

    ctos(char ascii, char str[])　　/* 把字符的ASCII码转换成字符串 */

{int i;

i = 2;
do {str[i--] = ascii%10 + '0';
    ascii /= 10;
} while (ascii > 0);
for (; i >= 0; i--) str[i] = ' ';
}

main()
{int k, key, row, col;
char ch1, ch2, str[]=" $";　　　/*　前面有3个空格　*/
char msg1[]="This is a simple screen edidtor.$",
msg2[]="You can move cursor by Arrow keys, TAB/Shift-TAB, Home and End.$",
msg3[]="You can press ^W for scroll up or ^Z for scroll down.$",
msg4[]="It has some functions, such as insert/modify a char.$",
msg5[]="If you press a function key, or key combined with Alt, Ctrl, Shift, it will display the key's scan code.$",
msg6[]="The program exits when you press ESCAPE.$";

cls(UP_SCROLL, 0, 0, 24, 79, 0, 7);
disp_string(0, 0, msg1); disp_string(2, 0, msg2);
disp_string(4, 0, msg3); disp_string(6, 0, msg4);
disp_string(8, 0, msg5); disp_string(11, 0, msg6);
row = col = ch1 = insert = 0;
locate(row, col);

while (ch1 != ESCAPE)
  {while (ch1 != ESCAPE)
    {if (!bioskey(1)) {check_key_state(); continue;}
     key = bioskey(0);
     ch1 = key; ch2 = key >> 8;
     if (ch1 != 0)
       {switch(ch1)
        {case TAB_KEY:
               col = ((col&0xFFF8) + 8) %80;
               break;
        case CTRL_W:
              cls(DOWN_SCROLL, 0, 0, 24, 79, 1, 7);
              row = row + 1;
              break;
        case CTRL_Z:
              cls(UP_SCROLL, 0, 0, 24, 79, 1, 7);
              break;
        default:
              if (ch1 == ESCAPE) continue;
              if (insert) move_right(row, col, 1);
              write_char_attr(row, col, ch1, 31);
              col = (col+1+80) % 80;
              break;
      }
      locate(row, col);
      continue;
     }

switch (ch2)
{case UP_ARROW:
       row = (row-1+25) % 25;
       break;
case DOWN_ARROW:
       row = (row+1+25) % 25;
       break;
case LEFT_ARROW:
       col = (col-1+80) % 80;
       break;
case RIGHT_ARROW:
       col = (col+1+80) % 80;
       break;
case SHIFT_TAB:
       k = col & 0xFFF8;
       col = (col - ((k==0)? 8:k+80)) % 80;
       break;
case HOME_KEY:
       col = 0;
       break;
case END_KEY:
       col = 79;
       break;
case INSERT:
       insert_key(&insert);
       break;
default:
       ctos(ch2, str);
       k = strlen(str)-1;
       if (insert) move_right(row, col, k);
       disp_string(row, col, str);
       col = (col + k + 80) % 80;
       break;
}

    locate(row, col);
   }
  }
cls(UP_SCROLL, 0, 0, 24, 79, 0, 7);
}


12.4 习题

本章的学习内容和要求
本章的学习内容和要求
12.1 汇编语言的嵌入
12.1 汇编语言的嵌入
12.2 C语言程序的汇编输出
12.2 C语言程序的汇编输出
12.3 一个具体的例子
12.3 一个具体的例子
12.4 习题
12.4 习题

→ 习题

12.4 习题

12.1、把12.3节中的C语言程序改写成汇编语言程序。

12.2、编写C语言程序，输出下面表达式的值，要求该表达式的计算用嵌入汇编语言程序段的方法来实现(注：题中所有变量都是整型)。

1)、1230 + 'A' - a
2)、b * b – 4 * a * c
3)、(a + b) / c + d

 4)、9*c / 5 + 32
5)、(a % 9 + 89) * 8
6)、x * x + y * y

12.3、用汇编语言编写函数Display(Data)，其功能是在当前光标处显示无符号整数Data，然后，编写一个C语言程序调用Display来显示整型变量的值。

12.4、用汇编语言实现下列C语言标准函数，并在C语言程序中验证之(假设未指明的变量都是整型)。

1)、isalpha(int Ascii)　/*若Ascii是字母的Ascii码，则其函数值为真，否则为假*/

2)、isxdigit(int Ascii)
/*若Ascii是十六进制字符('0'~'9'、'A'~'F'和'a'~'f')，那么，其函数值为真，否则为假*/

3)、strlwr(char *s)　/*把字符串s中的字母转换成小写*/

4)、strchr(char *s1, int Ascii)
/*在字符串s1中查找是否存在字符Ascii。若不存在，则返回NULL(即0)，否则，返回指向该字符在字符串中位置的指针*/

5)、strncmp(char *s1, char *s2, int Len)
/*比较字符串s1和s2前Len个字符，若s1<s2，其值小于0；s1==s2，其值为0；否则，其值大于0*/

6)、strncpy(char *Dest, char *Src, int Len)　/*把Src串中前Len个字符拷贝到Dest中*/

7)、memset(void *Buff, int Data, int Len)　　/*把用Data填充Buff前Len个存储单元*/

12.5、编写一个C语言程序，用TCC/BCC命令生成汇编语言程序，分析C语言语句和汇编语言语句之间的实现关系。

12.6、编写一个C语言程序，求出2~100之内的所有素数(大于1，且只能被1和自身整除的数，称为素数)，然后把它改写汇编语言程序，并比较二者代码的。

12.7、编写一个C语言程序，求出2~999之内的所有能被9整除，且含有5的数，然后把它改写汇编语言程序。

12.8、用汇编语言编写一个过程Display(Data)，其功能为在当前光标处显示无符号整数Data，然后编写C语言程序调用之，以达到显示数据的作用。


存储器组

16位寄存器组

4个数据寄存器(AX、BX、CX和DX) | 2个变址寄存器(SI和DI) | 2个指针寄存器(SP和BP)
4个段寄存器(ES、CS、SS和DS) | 1个指令指针寄存器(IP) | 1个标志寄存器(Flags)

32位寄存器组

32位寄存器组除了包含了先前CPU的所有寄存器，并进行了寄存器位数扩充之外，还增加了2个16位的段寄存器：FS和GS。
4个数据寄存器(EAX、EBX、ECX和EDX) | 2个变址寄存器(ESI和EDI) | 2个指针寄存器(ESP和EBP)
6个段寄存器(ES、CS、SS、DS、FS和GS) | 1个指令指针寄存器(EIP) | 1个标志寄存器(EFlags)

返　回


CPU存储器的功能说明

1、数据寄存器

数据寄存器主要用来保存操作数和运算结果等信息，从而节省读取操作数所需占用总线和访问存储器的时间。

32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取，不会影响高16位的数据。这些低16位寄存器分别命名为：AX、BX、CX和DX，它和先前的CPU中的寄存器相一致。

4个16位寄存器又可分割成8个独立的8位寄存器(AX：AH-AL、BX：BH-BL、CX：CH-CL、DX：DH-DL)，每个寄存器都有自己的名称，可独立存取。程序员可利用数据寄存器的这种“可分可合”的特性，灵活地处理字/字节的信息。

寄存器AX和AL通常称为累加器(Accumulator)，用累加器进行的操作可能需要更少时间。累加器可用于乘、除、输入/输出等操作，它们的使用频率很高；
寄存器BX称为基地址寄存器(Base Register)。它可作为存储器指针来使用；
寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时，要用它来控制循环次数；在位操作中，当移多位时，要用CL来指明移位的位数；
寄存器DX称为数据寄存器(Data Register)。在进行乘、除运算时，它可作为默认的操作数参与运算，也可用于存放I/O的端口地址。

在16位CPU中，AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址，但在32位CPU中，其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果，而且也可作为指针寄存器，所以，这些32位寄存器更具有通用性。详细内容请见第3.8节——32位地址的寻址方式。

2、变址寄存器

32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI，对低16位数据的存取，不影响高16位的数据。

寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register)，它们主要用于存放存储单元在段内的偏移量，用它们可实现多种存储器操作数的寻址方式(在第3章有详细介绍)，为以不同的地址形式访问存储单元提供方便。

变址寄存器不可分割成8位寄存器。作为通用寄存器，也可存储算术逻辑运算的操作数和运算结果。

它们可作一般的存储器指针使用。在字符串操作指令的执行过程中，对它们有特定的要求，而且还具有特殊的功能。具体描述请见第5.2.11节。

3、指针寄存器

32位CPU有2个32位通用寄存器EBP和ESP。其低16位对应先前CPU中的SBP和SP，对低16位数据的存取，不影响高16位的数据。

寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register)，主要用于存放堆栈内存储单元的偏移量，用它们可实现多种存储器操作数的寻址方式(在第3章有详细介绍)，为以不同的地址形式访问存储单元提供方便。

指针寄存器不可分割成8位寄存器。作为通用寄存器，也可存储算术逻辑运算的操作数和运算结果。

它们主要用于访问堆栈内的存储单元，并且规定：
 | BP为基指针(Base Pointer)寄存器，用它可直接存取堆栈中的数据；
 | SP为堆栈指针(Stack Pointer)寄存器，用它只可访问栈顶。

4、段寄存器

段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成的，这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。

CPU内部的段寄存器：
 | CS——代码段寄存器(Code Segment Register)，其值为代码段的段值；
 | DS——数据段寄存器(Data Segment Register)，其值为数据段的段值；
 | ES——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值；
 | SS——堆栈段寄存器(Stack Segment Register)，其值为堆栈段的段值；
 | FS——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值；
 | GS——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值。

在16位CPU系统中，它只有4个段寄存器，所以，程序在任何时刻至多有4个正在使用的段可直接访问；在32位微机系统中，它有6个段寄存器，所以，在此环境下开发的程序最多可同时访问6个段。

32位CPU有两个不同的工作方式：实方式和保护方式。在每种方式下，段寄存器的作用是不同的。有关规定简单描述如下：

实方式： | 前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致，内存单元的逻辑地址仍为“段值：偏移量”的形式。为访问某内存段内的数据，必须使用该段寄存器和存储单元的偏移量。
保护方式： | 在此方式下，情况要复杂得多，装入段寄存器的不再是段值，而是称为“选择子”(Selector)的某个值。段寄存器的具体作用在此不作进一步介绍了，有兴趣的读者可参阅其它科技资料。

5、指令指针寄存器

32位CPU把指令指针扩展到32位，并记作EIP，EIP的低16位与先前CPU中的IP作用相同。

指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中，下次要执行的指令通常已被预取到指令队列中，除非发生转移情况。所以，在理解它们的功能时，不考虑存在指令队列的情况。

在实方式下，由于每个段的最大范围为64K，所以，EIP中的高16位肯定都为0，此时，相当于只用其低16位的IP来反映程序中指令的执行次序。


16/32位标志位

16位标志寄存器——共用了9个标志位，它们主要用来反映CPU的状态和运算结果的特征。标志位的分布如下表所示。

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF

32位标志寄存器——32位CPU也把标志寄存器扩展到32位，记为EFLAGS。它新增加了四个控制标志位，它们是：IOPL、NT、RF和VM，这些标志位在实方式下不起作用。其它标志位的位置和作用与先前的完全相同。
其主要标志位的分布如下表所示。

31 … 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
… … VM RF NT IOPL OF DF IF TF SF ZF AF PF CF

返回...


各种标志位的含义

一、运算结果标志位

1、进位标志CF(Carry Flag)

进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位，那么，其值为1，否则其值为0。

使用该标志位的情况有：多字(字节)数的加减运算，无符号数的大小比较运算，移位操作，字(字节)之间移位，专门改变CF值的指令等。

2、奇偶标志PF(Parity Flag)

奇偶标志PF用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数，则PF的值为1，否则其值为0。

利用PF可进行奇偶校验检查，或产生奇偶校验位。在数据传送过程中，为了提供传送的可靠性，如果采用奇偶校验的方法，就可使用该标志位。

3、辅助进位标志AF(Auxiliary Carry Flag)

在发生下列情况时，辅助进位标志AF的值被置为1，否则其值为0：

(1)、在字操作时，发生低字节向高字节进位或借位时；
(2)、在字节操作时，发生低4位向高4位进位或借位时。

对以上6个运算结果标志位，在一般编程情况下，标志位CF、ZF、SF和OF的使用频率较高，而标志位PF和AF的使用频率较低。

4、零标志ZF(Zero Flag)

零标志ZF用来反映运算结果是否为0。如果运算结果为0，则其值为1，否则其值为0。在判断运算结果是否为0时，可使用此标志位。

5、符号标志SF(Sign Flag)

符号标志SF用来反映运算结果的符号位，它与运算结果的最高位相同。在微机系统中，有符号数采用补码表示法，所以，SF也就反映运算结果的正负号。运算结果为正数时，SF的值为0，否则其值为1。

6、溢出标志OF(Overflow Flag)

溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围，则称为溢出，OF的值被置为1，否则，OF的值被清为0。

“溢出”和“进位”是两个不同含义的概念，不要混淆。如果不太清楚的话，请查阅《计算机组成原理》课程中的有关章节。

二、状态控制标志位

状态控制标志位是用来控制CPU操作的，它们要通过专门的指令才能使之发生改变。

1、追踪标志TF(Trap Flag)

当追踪标志TF被置为1时，CPU进入单步执行方式，即每执行一条指令，产生一个单步中断请求。这种方式主要用于程序的调试。

指令系统中没有专门的指令来改变标志位TF的值，但程序员可用其它办法来改变其值。

2、中断允许标志IF(Interrupt-enable Flag)

中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值，CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求，以及CPU内部产生的中断请求。具体规定如下：

(1)、当IF=1时，CPU可以响应CPU外部的可屏蔽中断发出的中断请求；

(2)、当IF=0时，CPU不响应CPU外部的可屏蔽中断发出的中断请求。

CPU的指令系统中也有专门的指令来改变标志位IF的值。

3、方向标志DF(Direction Flag)

方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中，还提供了专门的指令来改变标志位DF的值。

三、32位标志寄存器增加的标志位

1、I/O特权标志IOPL(I/O Privilege Level)

I/O特权标志用两位二进制位来表示，也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值，那么，该I/O指令可执行，否则将发生一个保护异常。

2、嵌套任务标志NT(Nested Task)

嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下：

(1)、当NT=0，用堆栈中保存的值恢复EFLAGS、CS和EIP，执行常规的中断返回操作；

(2)、当NT=1，通过任务转换实现中断返回。

3、重启动标志RF(Restart Flag)

重启动标志RF用来控制是否接受调试故障。规定：RF=0时，表示“接受”调试故障，否则拒绝之。在成功执行完一条指令后，处理机把RF置为0，当接受到一个非调试故障时，处理机就把它置为1。

4、虚拟8086方式标志VM(Virtual 8086 Mode)

如果该标志的值为1，则表示处理机处于虚拟的8086方式下的工作状态，否则，处理机处于一般保护方式下的工作状态。