汇编语言程序设计 简明教程.

Slides:



Advertisements
Similar presentations
3 的倍数的特征 的倍数有 : 。 5 的倍数有 : 。 既是 2 的倍数又是 5 的倍数有 : 。 12 , 18 , 20 , 48 , 60 , 72 , , 25 , 60 ,
Advertisements

2 和 5 的倍数的特征 运动热身 怎样找一个数的倍数? 从小到大写出 2 的倍数( 10 个): 写出 5 的倍数( 6 个) 2 , 4 , 6 , 8 , 10 , 12 , 14 , 16 , 18 , 20 5 , 10 , 15 , 20 , 25 , 30.
练一练: 在数轴上画出表示下列各数的点, 并指出这些点相互间的关系: -6 , 6 , -3 , 3 , -1.5, 1.5.
输入输出程序设计 输入输出的基本概念 无条件方式输入输出 查询方式输入输出 中断方式输入输出.
微型计算机技术 教 学 指 导(七) 太原广播电视大学 郭建勇.
第10章 DOS功能调用与BIOS中断调用.
第7章 8086/8088汇编语言程序设计 7.1 引言 7.2 顺序程序设计 7.3 分支结构程序设计 7.4 循环结构程序设计
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
本周实验安排 实验内容:(P231)人名排序的例子。
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年10月30日.
第3章 80x86汇编语言程序设计(下).
4.1 汇编语言 4.2 顺序结构程序 4.3 分支程序设计 4.4 循环程序设计 4.5 子程序设计
9.1 可编程并行输入/输出接口芯片8255A 9.2 可编程计数器/定时器 可编程串行输入/输出接口芯片8251A
3.3.5 程序控制指令 控制转移指令分为: 转移指令 循环控制指令 调用和返回指令 中断指令.
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月26日.
第5章 循环与分支程序设计  循环程序设计  分支程序设计.
汇编语言程序设计 Assembly Language Programming
第三章 寻址方式与指令系统 3.1 寻址方式 一条指令通常由两大部分构成: 操作码 操作数
第九章 计数器和定时器电路 第一节 概述 第二节 Intel 8253的控制字 第三节 Intel 8253的工作方式 第九章 计数器和定时器电路 第一节 概述 第二节 Intel 8253的控制字 第三节 Intel 8253的工作方式 第四节 Intel 8253在IBM PC机上的应用.
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年12月10日.
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月05日.
输入输出与中断 主要内容 CPU与外设之间数据传送方式 中断技术 8086中断系统和中断处理.
第八章 输入输出程序设计 总线 CPU MEM I/O接口 I/O设备.
第3章 IA-32指令系统 3.1 基本数据类型 3.2 IA-32的指令格式 3.3 IA-32指令的操作数寻址方式
走进编程 程序的顺序结构(二).
微机原理与接口技术 ——80x86微处理器 西安邮电大学 计算机学院 范琳.
第一章 8086程序设计 第二章 MCS-51程序设计 第三章 微机基本系统的设计 第四章 存贮器与接口 第五章 并行接口
汇编语言程序设计课程设计 第二次实验 DEBUG基本命令与算术运算指令
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
微型计算机原理及应用.
第3章 微型计算机输入输出接口 3.1 输入/输出接口 3.2 输入输出数据传输的控制方式 3.3 开关量输入输出接口 欢迎辞.
第六章 子程序结构 §6.1 子程序的设计方法 §6.2 嵌套与递归子程序 §6.3 子程序举例 §6.4 DOS系统功能调用.
第五章 循环与分支程序设计 §5.1 循环与转移指令 §5.2 循环程序设计 §5.3 分支程序设计.
條件處理.
第5章 循环与分支程序设计 学习目标: 了解并掌握循环程序的构造方法,尤其是对循环控制条件的设置以及可能出现的边界情况的考虑。掌握起泡排序算法这种多重循环程序设计中的常用方法。交换标志位的设置在此算法中更能提高效率。学会在数组排序算法中采用折半查找法来提高查找效率。学会使用跳跃表法实现CASE结构。
4.1 汇编语言程序格式 4.2 MASM中的表达式 4.3 伪指令语句 4.4 DOS系统功能调用和BIOS中断调用
3.4.5 控制转移指令(Control transfer instructions) (一)、控制转移指令概述
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月19日.
第4章 汇编语言程序格式  汇编程序功能  伪操作  汇编语言程序格式  汇编语言程序的上机过程.
第10章 可编程外围接口芯片8255A及其应用 10.1 概述 A的工作原理 A的应用举例.
C语言程序设计 主讲教师:陆幼利.
习题3 1、 分别说明下列指令的原操作数和目的操作数各采用什么寻址方式。 设定如下: ①立即寻址 ② ① ②寄存器寻址
第九章 BIOS和DOS中断 在存储器系统中,从地址0FE000H开始的8K ROM(只读存储器)中装有BIOS(Basic Iuput /output System)例行程序。驻留在ROM中的BIOS给PC系列的不同微处理器提供了兼容的系统加电自检,引导装入,主要I/O设备的处理程序以及接口控制等功能模块来处理所有的系统中断。使用BIOS功能调用,给程序员编程带来很大方便,程序员不必了解硬件操作的具体细节,直接用指令设置参数,然后中断调用BIOS中的子功能,所以利用BIOS功能编写的程序简洁,可读性好,
第2章 80x86计算机组织  计算机系统  存储器  中央处理机  外部设备.
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月13日.
《微型计算机原理与接口技术》 第4版 王良 宁德师范学院 吴宁 乔亚男 编著 清华大学出版社 出版
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
C语言程序设计 第一章 数据类型, 运算符与表达式 第二章 顺序程序设计 第三章 选择结构程序设计 第四章 循环控制 第五章 数组.
第5章 循环与分支程序设计  循环程序设计  分支程序设计.
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年12月17日.
成绩是怎么算出来的? 16级第一学期半期考试成绩 班级 姓名 语文 数学 英语 政治 历史 地理 物理 化学 生物 总分 1 张三1 115
第六章 Excel的应用 一、Excel的单元格与区域 1、单元格:H8, D7, IV26等 2、区域:H2..D8, HS98:IT77
第4章 Excel电子表格制作软件 4.4 函数(一).
实验三 16位算术逻辑运算实验 不带进位控制的算术运算 置AR=1: 设置开关CN 1 不带进位 0 带进位运算;
本节内容 内存复制指令 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
3.16 枚举算法及其程序实现 ——数组的作用.
College of Computer Science & Technology
第4课时 绝对值.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
微机原理与接口技术 微机原理与接口技术 朱华贵 2015年11月06日.
3. 逻辑运算指令 A、简单逻辑操作指令 CLR A. (不影响CY、AC、 OV标志) CPL A
循环程序设计 在程序中包含重复执行的程序段称为循环程序设计。循环程序可以使程序结构性强、可读性好,从而大大提高了程序质量。
数据表示 第 2 讲.
第6章 子程序结构 在程序设计中,我们会发现一些多次无规律重复的程序段或语句序列。解决此类问题一个行之有效的方法就是将它们设计成可供反复调用的独立的子程序结构,以便在需要时调用。在汇编语言中,子程序又称过程。 调用子程序的程序称为主调程序或主程序。 2019/7/20 ch6.
4.3 汇编语言程序设计 顺序程序设计 顺序程序设计是最基本的程序设计。它是按照指令排列的先后顺序依次执行,每条指令都必须执行,且只执行一遍。顺序程序设计一般比较单一、简单,常常作为复杂程序的一部分。
微机原理与接口技术 第5章 汇编语言程序设计 西安邮电大学计算机学院 王 钰.
第4章 汇编语言程序格式  汇编程序功能  伪操作  汇编语言程序格式  汇编语言程序的上机过程
§4.5 最大公因式的矩阵求法( Ⅱ ).
第三章 8086的指令系统 8086指令特点 8086的寻址方式 8086的指令格式及数据类型 8086的指令集.
顺序结构程序设计 ——关于“字符串”和数值.
Presentation transcript:

汇编语言程序设计 简明教程

第四章 选择和循环 4.1 测试和控制指令 4.2 选择结构程序 4.3 循环结构程序 4.4 程序的调试 习题四

按照指令执行的顺序,程序的结构可以划分成以下三种。 顺序结构:程序按照它编写的顺序执行,每条指令只执行一 次,这样的程序称为“顺序结构”的程序。 循环结构:一组指令被反复地执行,这样的程序称为“循环结 构”或者“重复结构”的程序。 选择结构:根据某个条件,一部分指令被执行,另一部分指 令没有被执行,这样的程序称为“选择结构”或者 “分支结构”的程序。 一个实际运行的程序,常常是由以上三种结构的程序组合而成的,上面的三种结构称为程序的“基本结构”。使用这三种基本结构,可以编写出任何所需要的程序。

4.1 测试和转移控制指令 4.1.1 无条件转移指令 4.1.2 比较和测试指令 4.1.3 条件转移指令

4.1.1 无条件转移指令 无条件转移指令的一般格式: JMP 目的位置 执行JMP指令后,程序转移到新的“目的位置”执行。

[例4-1] 用JMP指令实现转移 CODE SEGMENT ASSUME CS: CODE START: MOV DL, 20H ONE: MOV AH, 2 INT 21H ;输出DL中的字符 INC DL ;修改DL中的字符代码 JMP ONE ;转移到“ONE”处继续执行 MOV AX, 4C00H INT 21H CODE ENDS END START

1.近程无条件转移指令 如果转移的目的位置与出发点在同一个段里,这样的转移称为“近程”转移或者“段内”转移。实现“近程”转移,实质上是把目标位置的“偏移地址”置入IP寄存器。 按照寻址方式的不同,近程无条件转移指令有三种格式。

由于用一个字节补码表示目的地址与当前地址的距离,所以转移范围为下一条指令地址-128 ~ +127字节以内。 (1)短转移 如果目的位置离开出发点很近,可以使用以下格式: JMP SHORT LABEL 其中“LABEL”是目的位置的标号。这种格式产生的机器指令代码最短,为2字节。 100H: JMP SHORT TWO 102H: …… …… 10CH: TWO: …… 指令“JMP SHORT TWO”汇编后产生的机器指令为“EB0A”。 “EB”是这种类型转移指令的“操作码”,“0A”是目的位置离开出发点的距离,10CH-102H=0AH。 由于用一个字节补码表示目的地址与当前地址的距离,所以转移范围为下一条指令地址-128 ~ +127字节以内。

使用近程直接转移指令可以实现同一个段内64KB范围的转移。 (2)近程直接转移 JMP 目的位置标号 100H: JMP TWO 103H: ONE: …… …… 0F000H: TWO:JMP ONE 0F003H: …… 指令“JMP TWO”汇编后得到的机器指令代码为“E9FDEE”。 “E9”为操作码 “0EEFDH”为位移量,0EEFDH=0F000H-103H。 指令“JMP ONE”对应的机器指令代码为“E90011” “E9”为操作码 位移量“1100H”,0F003H+1100H=0103H(舍去进位) 使用近程直接转移指令可以实现同一个段内64KB范围的转移。

把转移的目的地址事先存放在某个寄存器或存储器单元中,通过这个寄存器或存储单元实现转移。 (3)近程间接转移 把转移的目的地址事先存放在某个寄存器或存储器单元中,通过这个寄存器或存储单元实现转移。 JMP CX ;寄存器间接转移,可使用任何一个通用寄存器 JMP WORD PTR[BX] ;存储器间接转移,目的地址在存储单元中

已在数据段定义存储器单元“TARGET” : TAEGET DW ONE 下面四组指令都可以实现向标号“ONE”的转移: 1)JMP ONE ;近程直接转移 2)LEA DX, ONE JMP DX ;寄存器间接段内转移 3)LEA BX, TARGET JMP WORD PTR[BX] ;存储器间接段内转移 4)JMP TARGET ;存储器间接段内转移

2.远程无条件转移指令 远程无条件转移指令可以实现不同的段之间的转移,执行该指令时,CPU把目的段的段基址装入CS,目的位置的段内偏移地址装入IP。有直接寻址和间接寻址两种格式。 (1)远程直接转移 JMP FAR PTR 远程标号 指令汇编后,对应的机器指令为5个字节: 1个字节操作码“0EAH” 2个字节目的地址的段内偏移地址 2个字节目的标号所在段的段基址

远程转移需要32位的目的地址,使用间接转移时,需要把32位目的地址事先装入用“DD”定义的存储单元。 (2)远程间接转移 远程转移需要32位的目的地址,使用间接转移时,需要把32位目的地址事先装入用“DD”定义的存储单元。 假设已在数据段定义存储器单元“FAR_TGT”如下: FAR_TGT DD TWO 下面三组指令都可以实现向远程标号“TWO”的转移: 1)JMP FAR PTR TWO ;远程直接转移 2)LEA BX, FAR_TGT JMP DWORD PTR[BX] ;远程间接转移 3)JMP FAR_TGT ;远程间接转移

4.1.2 比较和测试指令 (1)CMP(Compare, 比较)指令 指令格式: CMP 目的操作数,源操作数 4.1.2 比较和测试指令 (1)CMP(Compare, 比较)指令 指令格式: CMP 目的操作数,源操作数 目的操作数:8位/16位/32位的寄存器/存储器操作数。 源操作数:与目的操作数同类型的寄存器/存储器/立即数。 功能:目的操作数-源操作数,保留运算产生的标志位,不保留 运算的差。用来比较两个有符号数或无符号数的大小。

假设(ECX)= 8090A0B0H,指令“CMP ECX, 0”执行后: ZF=0 (ECX)≠ 0 OF=0 减法操作没有产生溢出(SF是正确的结果符号位) SF=1 如果ECX中存放的是有符号数,这个数是负数 CF=0 如果ECX中存放的是无符号数,这个数大于0 这条指令与下面的指令等效: OR ECX, 0 ;根据ECX的值确定SF,ZF AND ECX, 0FFFFFFFFH ;根据ECX的值确定SF,ZF XOR ECX, 0 ;根据ECX的值确定SF,ZF

对于有符号数: OF=0时,SF为正确的结果符号 OF=1时,SF与正确的符号位相反 OF⊕SF的运算结果反映了正确的结果符号 对于无符号数: CF=0,目的操作数≥源操作数 CF= 1, 目的操作数<源操作数

假设存储器变量(X)= 80H,指令“CMP X, 5”执行后: ZF=0 (X)≠ 5 OF=0 减法操作没有产生溢出,SF是正确的结果符号位 SF=1 如果X中存放的是有符号数,X<5 (由于OF=0,所以符号标志SF有效/正确) CF=0 如果X中存放的是无符号数,X>5 (由于ZF=0,所以不相等)

(2)TEST(Test,测试)指令 指令格式: TEST 目的操作数,源操作数 目的操作数:8位/16位/32位的寄存器/存储器操作数。 源操作数:与目的操作数同类型的寄存器/存储器/立即数。 功能:TEST指令将目的操作数与源操作数进行逻辑乘运算,保 留运算产生的各标志位,但是不保留逻辑乘的结果。该 指令用来测试目的操作数中某几位二进制的特征。

指令 TEST VAR, 1 执行后: 如果ZF = 0,说明变量VAR的D0位为1,该数为奇数 如果ZF = 1,说明变量VAR的D0位为0,该数为偶数 指令 TEST BL, 6 执行后: 如果ZF = 0, 说明BL寄存器的D2D1≠00, 这两位为01, 10或11。 如果ZF = 1,说明BL寄存器的D2D1=00,这两位为00。

(3)BT(Bit Test,位测试)指令 指令格式: BT 目的操作数,源操作数 目的操作数: 16位/32位的寄存器/存储器操作数。 源操作数:与目的操作数同类型的寄存器操作数或0~255以内 的立即数。 功能:BT指令将目的操作数内部由源操作数指定的那一位二进 制的值送入CF,两个操作数的值均不改变。该指令用来 测试目的操作数中某一位二进制的值。

设(EDX)= 12345678H, (ECX)= 5 与BT指令类似的还有以下三条指令: BT EDX, ECX ; 由于EDX寄存器D5=1,执行后CF= 1,EDX值不变 BT EDX, 2 ; 由于EDX寄存器D2=0,执行后CF= 0,EDX值不变 与BT指令类似的还有以下三条指令: BTS 目的操作数,源操作数 ;测试目的操作数的指定位,并把该位置为1 BTR 目的操作数,源操作数 ;测试目的操作数的指定位,并把该位置为0 BTC 目的操作数,源操作数 ;测试目的操作数的指定位,并把该位取反

4.1.3 条件转移指令 条件转移指令格式: “J”是条件转移指令操作码的第一个字母 “cc”是代表转移条件的1~3个字母 4.1.3 条件转移指令 条件转移指令格式: Jcc label “J”是条件转移指令操作码的第一个字母 “cc”是代表转移条件的1~3个字母 “label”是转移目的地的标号。

(1)根据两个有符号数比较结果的条件转移指令 两个有符号数的比较结果通过OF,SF,ZF反映出来 G (Greater,大于) L (Less,小于) E (Equal,等于) N (Not,否) 例:JG/JNLE 大于(不小于等于)则转移 JNL/JGE 不小于(大于或等于)则转移 JE/JZ 等于(为零)则转移

根据有符号数大小的条件转移指令 指令助记符 指令功能 转移条件 JG, JNLE 大于(不小于等于)时转移 OF⊕SF= 0且ZF=0 JGE, JNL 大于等于(不小于)时转移 OF⊕SF= 0 JZ, JE 为零(相等)时转移 ZF= 1 JNZ, JNE 不为零(不相等)时转移 ZF=0 JL, JNGE 小于(不大于等于)时转移 OF⊕SF= 1 JLE, JNG 小于等于(不大于)时转移 OF⊕SF= 1或ZF=1

下面程序根据有符号字变量X和Y的大小决定程序的走向。 MOV AX, X ;取出X的值送AX CMP AX, Y ;比较两个操作数,建立需要的标志位 JG GREATER ;如果X>Y,转移到 “GREATER”处执行 JE EQUAL ;如果X=Y,转移到 “EQUAL”处执行 LESS: ;否则,执行标号“LESS”处的指令 …… GREATER: EQUAL:

下面程序能够正确运行,但最后一条指令有“画蛇添足”之嫌。 JG GREATER ;如果X>Y,转移到 “GREATER”处 JE EQUAL ;如果X=Y,转移到 “EQUAL”处 JL LESS ;如果X<Y,转移到 “LESS”处 LESS: …… 下面的程序计算 AX = |AX-BX| SUB AX, BX ;AX←(AX)-(BX), 建立标志位 JGE SKIP ;如果(AX)≥0,转标号“SKIP” NEG AX ;如果(AX)<0,把AX的值取反 SKIP:

(2)根据两个无符号数比较结果的条件转移指令 两个有符号数的比较结果通过CF,ZF反映出来,代表转移条件的字母: A(Above,高于) B(Below,低于) E(Equal,等于)

根据无符号数大小的条件转移指令 指令助记符 指令功能 转移条件 JA, JNBE 高于(不低于等于)时转移 CF= 0且ZF=0 JAE, JNB, JNC 高于等于(不低于)时转移 CF= 0 JZ, JE 为零(相等)时转移 ZF= 1 JNZ, JNE 不为零(不相等)时转移 ZF=0 JB, JNAE, JC 低于(不高于等于)时转移 CF= 1 JBE, JNA 低于等于(不高于)时转移 CF= 1或ZF=1

(3)根据单个标志位的条件转移指令 指令操作码助记符 指令功能 转移条件 JC, JB, JNAE 有进位时转移 CF= 1 JNC, JNB, JAE 无进位时转移 CF= 0 JZ, JE 为零(相等)时转移 ZF= 1 JNZ, JNE 不为零(不相等)时转移 ZF=0 JS 为负时转移 SF=1 JNS 为正时转移 SF=0 JO 溢出时转移 OF=1 JNO 不溢出时转移 OF=0 JP, JPE “1”的个数为偶数时转移 PF=1 JNP, JPO “1”的个数为奇数时转移 PF=0

对于16位80X86CPU,条件转移指令的转移范围在下一条指令地址-128~+127字节之间。如果转移目的位置超出了上述范围,汇编时将报告错误。 JG Label ;如果标号“Label”超出范围,汇编时将出错 可以把上面指令修改为: JNG Skip JMP Label Skip: …… 对于32位80X86CPU,条件转移指令汇编后产生4字节机器代码,前2字节是它的操作码,后面2字节是表示转移距离的位移量,可以实现64KB范围内的转移。

(4)根据CX/ECX寄存器值的条件转移指令 指令格式: JCXZ Label ;若CX=0,转移到Label JECXZ Label ;若ECX=0,转移到Label 它们的转移范围固定为下一条指令地址-128~+127字节以内。

4.2 选择结构程序 4.2.1 基本选择结构 4.2.2 单分支选择结构 4.2.3 复合选择结构 4.2.4 多分支选择结构

计算分段函数的值 为“|X|>3”和“|X|≤3”分别编制了进行不同处理的指令序列。 如果条件“|X|≤3”成立(为“真”),执行 “Y=3X-5” 如果条件“|X|≤3”不成立(为“假”),执行Y=6 通过在不同的程序之间进行选择,实现程序的不同功能, “选择结构”因此得名。

4.2.1 基本选择结构

[例4-2] 判断变量X的值是否为“偶数” CODE SEGMENT ASSUME CS: CODE START: JMP BEGIN X DB ? ; 被测试的数,汇编之前置入 YES DB 0AH, 0DH, “It’s a even number.”, 0AH, 0DH, ‘$’ NO DB 0AH, 0DH, “It’s a odd number.”, 0AH, 0DH, ‘$’ BEGIN:PUSH CS POP DS

TEST X, 1 ;测试X的最低位,确定是否为偶数 JZ EVN ;ZF=1,该数是偶数,转向“EVN” ODD: LEA DX, NO ;否则,该数是奇数 MOV AH, 9 INT 21H ;输出奇数的相关信息 JMP DONE ;跳过程序“EVN” EVN: LEA DX, YES INT 21H ;输出偶数的相关信息 DONE: MOV AX, 4C00H INT 21H CODE ENDS END START

如果两个“平行”分支有相同的处理过程,可以把它们“合并” : …… TEST X, 1 ;测试X的最低位,确定是否为偶数 JZ EVN ;ZF=1,该数是偶数,转向“EVN” ODD: LEA DX, NO ;否则,该数是奇数 JMP DONE ;跳过程序“EVN” EVN: LEA DX, YES ;该数是偶数 DONE: MOV AH, 9 INT 21H ;输出该数的相关信息 MOV AX, 4C00H

变量X取值93H,汇编、连接后运行该程序,程序输出: It’s a odd number. 变量X取值94H,汇编、连接后运行该程序,程序输出: It’s a even number.

[例4-3] 从键盘上输入一个小写字母,显示该字母的前导和后继。 DATA SEGMENT PROMPT DB 0DH, 0AH, “ Input a lowercase letter: $”;提示 ERR_MSG DB 0DH, 0AH, “ Input error . $” ;输入错误警告 BUF DB 0DH, 0AH, ‘Prev: ’ ;输出缓冲区 PREV DB 20H DB 0DH, 0AH, ‘Succ: ’ SUCC DB 20H DB 0DH, 0AH, ‘$’ DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA

START: MOV AX, DATA MOV DS, AX INPUT: LEA DX, PROMPT MOV AH, 9 INT 21H ;输出提示信息 MOV AH, 1 INT 21H ;输入一个字符 CMP AL, ‘a’ ;输入正确性检查 JB ERROR CMP AL, ‘z’ JA ERROR MOV BL, AL ;计算“前导”字母 DEC BL

CMP BL, ‘a’ JB SKIP1 ;“前导”非字母,跳过 MOV PREV, BL ;保存“前导”字母 SKIP1: INC AL ;计算“后继”字母 CMP AL, ‘z’ JA SKIP2 ;“后继”非字母,跳过 MOV SUCC, AL ;“后继”为字母,保存 SKIP2: LEA DX, BUF ;输出“前导”和“后继”字母 MOV AH, 09H INT 21H JMP EXIT ;跳过出错处理程序

程序要点: ERROR:LEA DX, ERR-MSG ;显示出错信息 MOV AH, 09H INT 21H JMP INPUT ;要求重新输入 EXIT: MOV AX, 4C00H ;返回OS CODE ENDS END START 程序要点: 健壮性 预设结果

[例4-4] 计算分段函数 INCLUDE YLIB.H DATA SEGMENT PROMPT DB 0DH, 0AH, “Input X ( -10000~+10000 ): $” X DW ? OUT_MSG DB 0DH, 0AH, “Y= $” DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX ;装载DS

LEA DX, PROMPT ;输入提示信息 CALL READINT ;从键盘上输入X的值 MOV X, AX ;保存输入值 COMP: CMP X, 3 ;比较,X>3 ? JG GREATER ;X>3成立,转“GREATER” CMP X, -3 ;比较,X<-3 ? JL GREATER ;X<-3成立,转“GREATER” LESS: ;|X|≤3 的程序段 MOV BX, AX ;BX←X SAL AX, 1 ;AX←2X ADD AX, BX ;AX←2X+X SUB AX, 5 ;AX←3X-5 JMP OUTPUT

GREATER: MOV AX, 6 ;|X|>3 的程序段 OUTPUT: LEA DX, OUT_MSG ;结果的前导文字 CALL WRITEINT ;输出计算结果 CALL CRLF ;输出回车换行 EXIT: MOV AX, 4C00H INT 21H CODE ENDS END START

复合逻辑表达式的分解

4.2.2 单分支选择结构 如果选择结构的一个分支为“空”,这样的程序流程称为 “单分支选择结构”。 4.2.2 单分支选择结构 如果选择结构的一个分支为“空”,这样的程序流程称为 “单分支选择结构”。 合理地选择Jcc指令所使用的条件,可以使程序更加流畅。 可以把一些基本选择结构程序改写为单分支选择结构。

计算AX ← |AX| 的两种判断方法:

在例4-4中,可以将Y“预设”为6,一旦条件|X|>3成立,立即转向OUTPUT输出“预设”的结果,否则进行相应的计算。 …… COMP: MOV AX, 6 ;预设AX= 6 CMP X, 3 ;比较,X>3 ? JG OUTPUT ;若X>3,转OUTPUT输出 CMP X, -3 ;比较,X<-3 ? JL OUTPUT ;若X<-3, 转OUTPUT输出 LESS: ;|X≤3 的程序段 MOV AX, X ;AX←X …… ;AX←3X-5 OUTPUT: LEA DX, OUT_MSG ;结果的前导文字 CALL WRITEINT ;输出计算结果

[例4-5] 将4位二进制转换成对应的十六进制字符 [例4-5] 将4位二进制转换成对应的十六进制字符 MOV AL, X CMP AL, 9 JA ALPH ADD AL, 30H JMP DONE ALPH: ADD AL, 37H DONE: MOV Y, AL MOV AL, X OR AL, 30H CMP AL, ‘9’ JBE DONE ADD AL, 7 DONE: MOV Y, AL

4.2.3 复合选择结构 选择结构一个分支的程序中又出现了选择结构,这样的 结构称为“复合选择结构”或者“嵌套选择结构”。 4.2.3 复合选择结构 选择结构一个分支的程序中又出现了选择结构,这样的 结构称为“复合选择结构”或者“嵌套选择结构”。 排除法:每次判断排除若干可能,留下一种可能情况进行处理; 确认法:每次判断确认一种可能,对已确认的情况进行处理。

[例4-6] 计算Y=SGN(X) ;方法a,逐项排除 CMP X, 0 JGE UN_MINUS MINUS: MOV Y, -1 JMP DONE UN_MINUS: JE ZERO MOV Y, 1 ZERO: MOV Y, 0 DONE: …… ;方法b,逐项确认 CMP X, 0 JG PLUS JE ZERO MINUS: MOV Y, -1 JMP DONE PLUS: MOV Y, 1 ZERO: MOV Y, 0 DONE: ……

复合分支选择结构

4.2.4 多分支选择结构 在选择结构程序里,如果可供选择的程序块多于两个,这样的结构称为多分支选择结构,如下图 (a)所示,下图 (b)是汇编语言程序的实现方法。

[例4-7] 从键盘上输入数字“1”到“3”,根据输入选择对应程序 块执行。 [例4-7] 从键盘上输入数字“1”到“3”,根据输入选择对应程序 块执行。 DATA SEGMENT PROMPT DB 0DH, 0AH, “Input a number (1~3): $” MSG1 DB 0DH, 0AH, “FUNCTION 1 EXECUTED . $” MSG2 DB 0DH, 0AH, “FUNCTION 2 EXECUTED . $” MSG3 DB 0DH, 0AH, “FUNCTION 3 EXECUTED . $” DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

INPUT: LEA DX, PROMPT MOV AH, 9 INT 21H ;输出提示信息 MOV AH, 1 INT 21H ;输入一个数字 CMP AL, ‘1’ JB INPUT ;“0”或非数字,重新输入 JE F1 ;数字“1”,转F1 CMP AL, ‘2’ JE F2 ;数字“2”,转F2 CMP AL, ‘3’ JE F3 ;数字“3”,转F3 JMP INPUT ;大于“3”,重新输入

F1: LEA DX, MSG1 ;F1程序块 JMP OUTPUT F2: LEA DX, MSG2 ;F2程序块 F3: LEA DX, MSG3 ;F3程序块 OUTPUT: MOV AH, 9 INT 21H MOV AX, 4C00H CODE ENDS END START

把完成各功能的程序块入口地址放在一张表格中,根据输入,计算出该功能程序块入口地址在表中的位置,通过存储器间接转移转入对应位置执行。 DATA SEGMENT PROMPT DB 0DH, 0AH, “Input a number (1~3): $” MSG1 DB 0DH, 0AH, “FUNCTION 1 EXECUTED . $” MSG2 DB 0DH, 0AH, “FUNCTION 2 EXECUTED . $” MSG3 DB 0DH, 0AH, “FUNCTION 3 EXECUTED . $” ADDTBL DW F1, F2, F3 DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA

START:MOV AX, DATA MOV DS, AX INPUT:LEA DX, PROMPT MOV AH, 9 INT 21H ;显示提示信息 MOV AH, 1 INT 21H ;输入一个数字 CMP AL, ‘1’ JB INPUT ;不正确输入,重新输入 CMP AL, ‘3’ JA INPUT ;不正确输入,重新输入

SUB AL, ‘1’ ;将数字字符“1”到“3”转换为0, 1, 2 SHL AL, 1 ;转换为0,2,4 MOV BL, AL MOV BH, 0 ;转入BX JMP ADDTBL[BX] ;间接寻址,转移到对应程序块 F1: LEA DX, MSG1 ;F1程序块 JMP OUTPUT F2: LEA DX, MSG2 ;F2程序块 F3: LEA DX, MSG3 ;F3程序块 JMP OUTPUT ;这条指令可以省略

OUTPUT: MOV AH, 9 INT 21H MOV AX, 4C00H CODE ENDS END START

4.3 循环结构程序 4.3.1 循环指令 4.3.2 计数循环 4.3.3 条件循环 4.3.4 多重循环

循环结构也称为“重复结构”,它使得一组指令重复地执行,可以用有限长度的程序完成大量的处理任务,几乎所有的应用程序中都离不开循环结构。 循环一般由以下4个部分组成: (1)初始化部分:为循环做准备,如累加器清零,设置地址指 针和计数器的初始值等。 (2)工作部分:实现循环的基本操作,也就是需要重复执行的 一段程序。 (3)修改部分:修改指针、计数器的值,为下一次循环做准备。 (4)控制部分:判断循环条件,结束循环或继续循环。

按照循环结束的条件,有以下两类循环: 计数循环:循环的次数事先已经知道,用一个变量(寄存器 或存储器单元)记录循环的次数(称为“循环计数 器”)。进行减法计数时,循环计数器的初值直接设为 循环次数,每循环一次将计数器减1,计数器减为0时, 循环结束。 条件循环:循环的次数事先并不确定,每次循环开始时或结 束后测试某个条件,根据这个条件是否满足来决定是 否继续下一次循环。

按照循环结束判断在循环中的位置,有以下两种结构的循环: WHILE循环:进入循环后,先判断循环结束条件,条件满足 则退出循环,循环次数最少为0次。 DO-WHILE循环:进入循环后,先执行工作部分,然后判断循环 继续的条件,条件满足则转向工作部分继续循环,循环 次数最少1次。

4.3.1 循环指令 循环指令采用相对寻址方式,Label距离循环指令的下一条 指令必须在-128~+127B之内。 4.3.1 循环指令 LOOP Label ; CX←CX-1,若(CX)≠0,转移到Label LOOPZ/LOOPE Label ; CX←CX-1,若(CX)≠0且ZF=1,转移到Label LOOPNZ/LOOPNE Label ; CX←CX-1,若(CX)≠0且ZF=0,转移到Label 循环指令采用相对寻址方式,Label距离循环指令的下一条 指令必须在-128~+127B之内。

LOOP指令的功能可以用Jcc指令实现: DEC CX ; CX←CX-1 JNZ Label ; 若(CX)≠0(也就是ZF=0),转移到Label LOOPZ/LOOPE,LOOPNZ/LOOPNE指令的功能也可以由 Jcc指令实现。 由于对CX先减1,后判断,如果CX的初值为0,将循环 65536次。 循环指令的执行不影响标志位。

4.3.2 计数循环 计数循环是基本的循环组织方式,用循环计数器的值来控制循环,有时候也可以结合其它条件共同控制。 4.3.2 计数循环 计数循环是基本的循环组织方式,用循环计数器的值来控制循环,有时候也可以结合其它条件共同控制。 [例4-8] 从键盘上输入一个字符串(不超过80个字符),将它 逆序后输出。 INCLUDE YLIB.H DATA SEGMENT BUFFER DB 81, ?, 81 DUP(?) MESS DB 0AH, 0DH, “Input a string please : $” DATA ENDS

CODE SEGMENT ASSUME CS:CODE, DS:DATA START:MOV AX, DATA MOV DS, AX LEA DX, MESS MOV AH, 09H INT 21H ; 输出提示信息 MOV AH, 0AH LEA DX, BUFFER INT 21H ; 输入字符串 CALL CRLF LEA BX, BUFFER ; 缓冲区首地址送BX MOV CL, BUFFER+1 MOV CH, 0 ; 输入字符个数送CX(循环次数)

ADD BX, CX INC BX ; 计算字符串末地址送BX(指针) DISP: MOV DL, [BX] MOV AH, 02H INT 21H ; 逆序输出一个字符 DEC BX ; 修改指针 LOOP DISP ; 计数循环 CALL CRLF ; 输出换行、回车,结束本行 MOV AX, 4C00H INT 21H CODE ENDS END START

[例4-9] 从键盘上输入一个十进制无符号整数,将它用十六进 制格式输出。 [例4-9] 从键盘上输入一个十进制无符号整数,将它用十六进 制格式输出。 INCLUDE YLIB.H DATA SEGMENT MESS1 DB 0AH, 0DH, “Input a number : $” MESS2 DB 0AH, 0DH, “The number in hexdecimal is: $” HEXTAB DB “0123456789ABCDEF” DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

LEA DX, MESS1 CALL READDEC ; 输入一个十进制无符号数 MOV SI, AX ; 转存在SI中 LEA DX, MESS2 MOV AH, 9 INT 21H ; 输出文字前导 MOV CX, 4 ; 循环计数器初值 LEA BX, HEXTAB ; 换码表首地址 ONE: PUSH CX MOV CL, 4 ROL SI, CL ; 把最高4位移到最低4位

MOV AX, SI ; 转入AX中 AND AX, 000FH ; 保留最低4位 XLAT ; 查表,转换成十六进制字符的ASCII代码 MOV DL, AL MOV AH, 2 INT 21H ; 输出一个十六进制字符 POP CX LOOP ONE ; 计数循环 CALL CRLF ; 输出回车换行, 结束本行 MOV AX, 4C00H INT 21H CODE ENDS END START

[例4-10] 从键盘上输入七名裁判的评分(0~10),扣除一个最. 高分,一个最低分,计算出其它五项评分的平均值(保留 [例4-10] 从键盘上输入七名裁判的评分(0~10),扣除一个最 高分,一个最低分,计算出其它五项评分的平均值(保留 一位小数),在显示器上输出。 用总分减去最高分、最低分,最后除以5,得到需要的成绩。 求N个数据中最大值的方法: 预设一个“最大值”, 取出一个数据与这个“最大值”进行比较, 如果数据大于“最大值”,则将该数据作为新的“最大值”。 进行N次比较之后留下的就是这N个数据的最大值。 预设的“最大值”的初值可以从N个数据中任取一个,也可以根 据数据的范围,取一个该范围内的最小的数。 计算最小值的方法与此类似。

INCLUDE YLIB.H DATA SEGMENT MESS1 DB 0DH, 0AH, “Input a score ( 0~10 ) : $” MESS2 DB 0DH, 0AH, “The final score is : $” C5 DB 5 MAX DB ? MIN DB ? SUM DB ? DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

MOV SUM, 0 ; 累加器清零 MOV MAX, 0 ; “最大值”预设为0 MOV MIN, 255 ; “最小值”预设为255 MOV CX, 7 ; 循环计数器,初值7 ONE: LEA DX, MESS1 CALL READDEC ; 键盘输入一个分数 ADD SUM, AL ; 累加 CMP MAX, AL ; 与“最大值”比较 JA L1 MOV MAX, AL ; 大于“最大值”则保留 L1: CMP MIN, AL ; 与“最小值”比较 JB L2 MOV MIN, AL ; 小于“最小值”则保留 L2: LOOP ONE ; 计数循环

MOV AL, SUM SUB AL, MAX SUB AL, MIN ; 从总分中减去最大、最小值 MOV SUM, AL XOR AH, AH ; 高8位清零 DIV C5 ; 求平均值 PUSH AX ; 保留余数(在AH中) MOV AH, 0 ; 清余数 LEA DX, MESS2 CALL WRITEDEC ; 输出结果的整数部分

MOV DL, ‘.’ MOV AH, 2 INT 21H ; 输出小数点 POP AX ; 从堆栈弹出余数 SHL AH, 1 ; 计算小数部分:(AH÷5)×10=AH×2 MOV DL, AH OR DL, 30H ; 转换成ASCII代码 INT 21H ; 输出结果的小数部分 CALL CRLF ; 输出回车换行,结束本行 MOV AX, 4C00H INT 21H CODE ENDS END START

[例4-11] 求稀疏矩阵ARRAY非零元素的平均值。 “稀疏矩阵”是指存在大量零元素的矩阵。 为了节约存储空间,通常不存储那些值为零的元素。 稀疏矩阵通常用一个“标尺”记录哪些位置上有非零元素。 标尺RULE = 0001 0000 0010 0000B表示一个16个元素的 稀疏矩阵,它仅在第3,10位置上有非零元素

.386 DATA SEGMENT USE16 ARRAY DW 123, -39, 211, …… ;稀疏矩阵非零元素 RULE DW 0001 0000 0011 0010B ;“标尺” AVG DW ? ;非零元素的平均值 SUM DD ? ;累加器 DATA ENDS CODE SEGMENT USE16 ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

LEA SI, ARRAY ;装载非零元素地址指针 MOV CX, 16 ;设置计数器初值 MOV BX, 0 ;设置非零元素个数计数器初值 MOV SUM, 0 ;设置累加器初值 ONE: ROL RULE, 1 ;测试标尺 JNC NEXT ;该位置上为零元素,转NEXT MOVSX EAX, WORD PTR[SI] ;取出一个元素,转换成32位有符号数 ADD SUM, EAX ;累加 INC BX ;统计非零元素个数 ADD SI, 2 ;修改地址指针 NEXT: LOOP ONE ;计数循环控制

CMP BX, 0 ;有非零元素? JE EMPTY ;无非零元素,转向“EMPTY” MOV DX, WORD PTR[SUM+2] MOV AX, WORD PTR[SUM] ;取出32位累加值 IDIV BX ;计算平均值 MOV AVG, AX ;保存结果 JMP EXIT EMPTY:MOV AVG, -1 ;无非零元素,置平均值为-1 EXIT: MOV AX, 4C00H INT 21H CODE ENDS END START

4.3.3 条件循环 [例4-12] 字符串STRING以代码0结束,求这个字符串的长度(字符个数)。 DATA SEGMENT 4.3.3 条件循环 [例4-12] 字符串STRING以代码0结束,求这个字符串的长度(字符个数)。 DATA SEGMENT STRING DB “A string for testing . ”, 0 LENTH DW ? DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START:MOV AX, DATA MOV DS, AX

LEA SI, STRING ;装载字符串指针 MOV CX, 0 ;设置计数器初值 TST: CMP BYTE PTR [SI], 0;比较 JE DONE ;字符串结束,转向DONE保存结果 INC SI ;修改指针 INC CX ;计数 JMP TST ;转向TST,继续循环 DONE: MOV LENTH, CX ;保存结果 MOV AX, 4C00H INT 21H CODE ENDS END START

比较一下,您喜欢这种风格吗? …… LEA SI, STRING-1 ;装载字符串指针 MOV CX, -1 ;装载计数器初值 TST: INC SI ;修改指针 INC CX ;计数 CMP BYTE PTR [SI], 0 ;比较 JNE TST ;未结束,转TST继续循环 MOV LENTH, CX ;字符串结束,保存结果

错在哪里?运行结果会怎样? …… TST: CMP BYTE PTR [SI], 0 ;比较 INC SI ;修改指针 INC CX ;计数 JNE TST ;转向TST,继续循环

[例4-13] 一维无符号字数组ARRAY以-1作为数组结束标志, 求这个数组各元素的平均值。 DATA SEGMENT ARRAY DW 1, 2, 3, 4, 5, 6, -1 AVRG DW ? DATA ENDS CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

LEA BX, ARRAY ;装载数组指针 XOR CX, CX ;设置计数器初值 XOR DX, DX XOR AX, AX ;清累加器 ONE: CMP WORD PTR[BX], -1 ;判数组是否结束 JE DONE ;数组结束, 转DONE,结束处理 ADD AX, [BX] ;累加 ADC DX, 0 ;保留进位 ADD BX, 2 ;修改指针 INC CX ;数组元素个数计数 JMP ONE

DONE: JCXZ NULL ;数组元素个数为0, 不能求平均值 DIV CX ;计算数组平均值 MOV AVRG, AX ;保存结果 JMP EXIT NULL: MOV AVRG, -1 ;数组为“空”,记平均值为-1 EXIT: MOV AX, 4C00H INT 21H CODE ENDS END START

[例4-14] 查找字母’a’在字符串STRING中第一次出现的位置, 如果未出现,置位置值为-1。 DATA SEGMENT POSITION DW ? STRING DB “This is a string for example. ” , 0 DATA ENDS CODE SEGMENT ASSUME DS: DATA, CS: CODE START: MOV AX, DATA MOV DS, AX

MOV SI, -1 ;SI用作字符串字符指针 MOV CX, 30 ;字符串长度30 L0: INC SI ;修改指针 CMP STRING[SI], ‘a’ ;一个字符与’a’进行比较 LOOPNE L0 ;字符串未结束,未找到,继续 JNE NOTFOUND ;未找到,转“NOTFOUND” MOV POSITION, SI ;保存位置值 JMP EXIT NOTFOUND:MOV POSITION, -1 ;未找到,置位置值为-1 EXIT: MOV AX, 4C00H INT 21H CODE ENDS END START

程序使用LOOPNE指令来控制循环,既有计数控制,又有条件控制。循环结束有两种可能性: 字符串内找到字符’a’:循环结束时ZF=1,SI内是字符的出 现位置(从0开始); 字符串内未找到字符’a’:循环结束时ZF=0,SI内是字符串 的长度-1(30-1=29)。 对于LOOPZ/LOOPE,LOOPNZ/LOOPNE控制的循环,一般应在循环结束后用条件转移指令分开这两种情况,分别处理。

[例4-15] 从键盘上输入一个有符号整数(假设在-32768 ~ +32767 之间),将它转换成二进制补码,存入NUM单元。 DATA SEGMENT C10 DW 10 NUM DW ? SIGN DB ? ERRMSG DB 0DH, 0AH, “Input a Decimal Digit ( 0~9 ) : $” DATA ENDS CODE SEGMENT ASSUME DS: DATA, CS: CODE START: MOV AX, DATA MOV DS, AX

MOV NUM, 0 ;累加器清零 MOV SIGN, 0 ;符号预设为0(表示“+”) BEGIN:MOV AH, 1 INT 21H ;从键盘输入一个字符 CMP AL, 0DH JE EXIT ;是回车,转EXIT,结束 CMP AL, “+” JE INPUT ;是“+”,转INPUT输入下一个字符 CMP AL, “-” JNE TWO ;非符号字符,转TWO处理该字符 MOV SIGN, 1 ;是“-”,把符号标识为1(表示“-”)

INPUT: MOV AH, 1 INT 21H ;从键盘再输入一个字符 CMP AL, 0DH JE DONE ;是回车,转DONE,结束处理 TWO: CMP AL, “0” JB ERRINPUT ;输入非数字,显示出错信息 CMP AL, “9” JA ERRINPUT ;输入非数字,显示出错信息 MOV BX, AX ;输入字符转移到BX寄存器 AND BX, 000FH ;转换成二进制数 MOV AX, NUM MUL C10 ADD AX, BX ;新输入数字拼接到已输入数字中 MOV NUM, AX JMP INPUT ;转INPUT,输入下一个字符

ERRINPUT: LEA DX, ERRMSG MOV AH, 9 INT 21H ;显示出错信息 JMP INPUT ;转INPUT,重新输入 DONE: CMP SIGN, 0 ;判符号位 JE EXIT NEG NUM ;符号为“-”,对已输入数求补 EXIT: MOV AX, 4C00H INT 21H CODE ENDS END START

4.3.4 多重循环 如果一个循环的循环体内包含了另一个循环,称这个循环为“多重循环”,各层循环可以是计数循环或者条件循环。

! “ # $ % & ‘ ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? [例4-16] 打印20H~7FH之间的ASCII字符表。 ! “ # $ % & ‘ ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h I j k l m n o p q r s t u v w x y z { | } ~

打印格式: 每行16个字符,共6行。 打印1行: 打印1个字符的过程重复16次,构成一个计数循环(内循环)。 打印6行: 打印1行字符的过程重复6次,构成另一个计数循环(外循环) 。

INCLUDE YLIB.H CODE SEGMENT ASSUME CS: CODE, DS: CODE START: MOV BL, 20H ;第一个字符的ASCII代码 MOV CH, 6 ;行数计数器初值 ; ============ 打印一行循环开始 ======= ====== L0: CALL CRLF ;开始一个新行 MOV CL, 16 ;列计数器初值

; ------------------- 打印一个字符的循环开始 --------------------- L1: MOV DL, BL ;装入一个字符ASCII代码 MOV AH, 2 INT 21H ;输出一个字符 MOV DL, 20H INT 21H ;输出一个空格 INC BL ;准备下一个待输出的ASCII码 DEC CL ;列数计数 L11: JNZ L1 ;列数未满(本行未完),转L1继续 ;--------------------- 打印一个字符的循环结束 ----------------------

DEC CH ;行数计数 L00: JNZ L0 ;行数未满,转L0继续 ; ============= 打印一行的循环结束 ============ CALL CRLF ;结束最后一行 MOV AX, 4C00H INT 21H CODE ENDS END START

借助于堆栈,将CX“分身”为两个计数器 …… MOV BL, 20H ;第一个字符的ASCII代码 MOV CX, 6 ;行数计数器初值 ; ============= 打印一行循环开始 ============== L0: CALL CRLF ;开始一个新行 PUSH CX ;保存CX中的行计数器值 MOV CX, 16 ;CX中置入列计数器初值

; -------------------- 打印一个字符的循环开始 -------------------- L1: MOV DL, BL ;装入一个字符ASCII代码 …… L11: LOOP L1 ;列数未满(本行未完),转L1继续 ; ------------------- 打印一个字符的循环结束 ----------------------- POP CX ;恢复CX为行计数器 L00: LOOP L0 ;行数计数,行数未满,转L0继续 ; ============== 打印一行的循环结束 ==============

[例4-17] 用“冒泡”的方法对数组P的元素排序,按从小到大的顺序排列。 遍数 本遍 整序前 第1次 整序后 第2次 第3次 第4次 1 32 16 84 8 5 16 32 84 8 5 16 32 8 84 5 16 32 8 5 84 2 16 8 32 5 84 16 8 5 32 84   3 8 16 5 32 84 8 5 16 32 84 4 5 8 16 32 84

INCLUDE YLIB.H DATA SEGMENT P DW 10 DUP(?) N DW ? MESS1 DB 0DH, 0AH, ‘Input Numbers of Elements ’ DB ‘in Array P (1~10): $’ MESS2 DB 0DH, 0AH, ‘Input Values of Elements in Array P: $’ MESS3 DB 0DH, 0AH, ‘NO. $’ MESS4 DB 0DH, 0AH, ‘Array P After Sort: ’, 0DH, 0AH, ‘$’ DATA ENDS

CODE SEGMENT ASSUME CS: CODE, DS: DATA START:MOV AX, DATA MOV DS, AX LEA DX, MESS1 CALL READINT ;输入数组元素个数 MOV N, AX LEA DX, MESS2 MOV AH, 9 INT 21H ;输出提示信息,准备输入数组各元素的值 ;****** 从键盘输入数组各元素 ************** MOV BX, 0 ;输入数组循环准备 MOV CX, N ; BX=元素在数组内位移, CX=元素个数

INPUT:LEA DX, MESS3 MOV AX, N SUB AX, CX INC AX CALL WRITEINT ;输出元素序号,从1开始 MOV DX, 0FFFFH CALL READINT ;输入一个元素 MOV P[BX], AX ;保存该元素的值 INC BX INC BX ;修改指针 LOOP INPUT ;输入未结束,转INPUT继续

;************* 开始排序 *************** MOV CX, N ;设置外层循环计数器 DEC CX ;CX中为排序的“遍数”(=N-1) ;========= 外层循环循环体开始 ============ LOOP1:PUSH CX ;保存外循环计数器 MOV BX, 0 ;BX=整序元素在数组内的位移 ;每一遍从第一个元素开始 ;----- 内层循环循环体开始,CX的值是内层循环的次数 -------- LOOP2:MOV AX, P[BX] CMP AX, P[BX+2] ;邻元素比较 JLE NEXT ;不需要整序,转NEXT XCHG AX, P[BX+2] ;交换邻元素位置 XCHG AX, P[BX]

NEXT: INC BX ;修改指针 INC BX LOOP LOOP2 ;本遍未结束,转LOOP2继续 ;------------------ 内层循环循环体结束 ------------------ POP CX ;恢复外层循环计数器 LOOP LOOP1 ;“遍数”未满,转LOOP1继续 ;========= 外层循环循环体结束 =============== ;********* 输出排序后的数组P ************** LEA DX, MESS4 MOV AH, 9 INT 21H ;输出提示信息 MOV BX, 0 MOV CX, N ;输出循环准备:BX=位移,CX=循环次数

OUTPUT: MOV DX, 0FFFFH MOV AX, P[BX] ;取出一个排序后的数组元素 CALL WRITEINT ;交“WRITEINT”输出 INC BX INC BX ;修改指针 LOOP OUTPUT ;输出未完成,转OUTPUT继续 CALL CRLF ;结束本行 MOV AX, 4C00H INT 21H CODE ENDS END START

该程序汇编,连接,运行(带下划线的内容从键盘输入): D:\TASM5>TASM SORT Assembling: sort.asm Error messages: none Warning messages: None Passes: 1 Remaining memory: 452k D:\TASM5>TLINK SORT, , , YLIB16 Turbo Link Version 7.1.30.1 Copyright © 1987,1996 Borland International Warning: No stack

D:\TASM5>SORT Input Number of Elements in Array P (1~10): 5 Input Values of Elements in Array P: NO. 1 32 NO. 2 16 NO. 3 84 NO. 4 8 NO. 5 5 Array P After Sort: 5   8 16 32 84

[例4-18] 从键盘上输入一个无符号字整数,分解出它的所有质因数,输出在显示器上。例如: 17=1*17 24=1*2*2*2*3 …… 解决这个问题的基本思路: 将键盘输入的数据被2除,如果整除,输出“*2”,将商继续被2 除,直到不能被2整除。 将余下的商被3除,如果整除,输出“*3”,将商继续被3除,直 到不能被3整除。 重复以上的过程,直到商为1。

INCLUDE YLIB.H DATA SEGMENT MESS1 DB 0DH, 0AH, 'Input a Number ( =0, Exit ): $' MESS2 DB '= 1$' DATA ENDS   CODE SEGMENT ASSUME CS: CODE, DS: DATA START: MOV AX, DATA MOV DS, AX

ONE: LEA DX, MESS1 ;从键盘输入一个无符号数 CALL READDEC CMP AX, 0 JZ EXIT ;如果输入数据为0,转EXIT结束程序  PUSH AX ;为了输出,保存AX中的输入数据 MOV DX, 0FFFFH CALL WRITEDEC ;输出这个数 LEA DX, MESS2 MOV AH, 9 INT 21H ;输出“=1”  MOV CX, 2 ;准备分解因数,预设除数CX=2 POP AX ;把输入数据从堆栈中弹出,恢复AX

TWO: CMP AX, 1 ;已分解结束(AX=1)? JE ONE ;分解结束,转ONE MOV DX, 0 ;通过除法试探是否含有该因数 PUSH AX ;除法进行之前保存AX的值 DIV CX CMP DX, 0 ;是否整除? JNZ THREE ;不含有该因数,转THREE POP SI ;整除,含有该因数,废弃堆栈里的数据 PUSH AX ;把除法的商置入堆栈保护 MOV DL, '*' ;输出“*” MOV AH, 2 INT 21H MOV AX, CX ;输出该因数

MOV DX, 0FFFFH CALL WRITEDEC POP AX ;恢复堆栈中保存的商 JMP TWO ;转TWO,继续试探分解同一个因数  THREE:POP AX ;试探不成功,从堆栈恢复原数据 INC CX ;除数加1,试探下一个因数 JMP TWO ;转TWO继续分解 EXIT: CALL CRLF ;处理结束,输出一个空行 MOV AX, 4C00H INT 21H CODE ENDS END START

4.4 程序的调试 4.4.1 程序调试的基本过程 4.4.2 语法错误的调试 4.4.3 程序测试 4.4.4 程序逻辑错误的调试

程序调试是为了找出程序中的错误,而不是着眼于证明程序是正确的。

4.4.1 程序调试的基本过程 源程序书写、输入可能发生错误(文字/语法错误); 4.4.1 程序调试的基本过程 导致程序出错的原因 源程序书写、输入可能发生错误(文字/语法错误); 对指令的功能,汇编语言中各种语句的作用理解有误,导致不正确地使用(语义错误); 对待求解问题的算法,特别是复杂问题的算法,有一个从“必然王国”到“自由王国”,逐步深入的认识过程(算法错误)。

(1)通过汇编、连接,发现并改正源程序中的语法错误。 中、小规模的程序的调试,可以分为如下三个步骤: (1)通过汇编、连接,发现并改正源程序中的语法错误。 (2)程序测试:运行这个程序,输入几组代表性数据,观察它的输出结果或程序产生的动作,找出它在功能上的错误。这一步仅仅是发现错误,还不能立即纠正错误。 (3)程序调试:针对已经发现的错误,通过多种方法,找出导致产生错误的原因,并改正这些错误。 上面的三个步骤可能需要重复多次,直到找不到新的错误。

4.4.2 语法错误的调试 汇编阶段发生的错误: 源程序输入错误 拼错保留字 阅读和分析出错信息 注意成对出现语句的配套检查 4.4.2 语法错误的调试 汇编阶段发生的错误: 源程序输入错误 拼错保留字 阅读和分析出错信息 注意成对出现语句的配套检查 错误信息过多时,产生列表文件 2. 连接阶段发生的错误: 模块之间的相互引用

4.4.3 程序测试 程序测试:运行这个程序,按照预定的方案,有目的地进行 典型数据的输入。观察程序的输出,判断程序能否完 成预定的功能。 4.4.3 程序测试 程序测试:运行这个程序,按照预定的方案,有目的地进行 典型数据的输入。观察程序的输出,判断程序能否完 成预定的功能。 程序测试阶段的效率取决于测试数据的选取,也就是测试方案的制定。

程序测试划分为两种类型: 正确性测试:输入正确范围内的数据,将输出结果与人工计算得到的正确结果进行比较,判断程序完成预定功能的正确性; 健壮性测试: 对程序输入“不正确”的数据,不应该导致程序“死循环”等其它不正确运行状态。 一个“健壮”的程序应能辨别输入数据的正确性,对“不正确”的输入数据不应产生貌似“正确”的结果。

测试数据应具有完整性,代表性。 测试数据的选取: 每一个“等价类”至少要选取一个(组)数据; 应包含每个“临界点”。

以计算分段函数为例: 正确性测试: 三个“等价类”: (-∞, -3), (-3, +3), (+3, +∞) 两个“临界点”:-3、+3。 这个程序至少应进行5组数据的测试: (-∞,-3), (-3, +3), (+3, +∞)三个区间内各一个数据 两个“临界点”数据+3和-3。

隐含的“等价类”: 等价类(-3,+3)应进一步划分为(-3,0]和[0,+3),这个划分主要针对X求绝对值算法的正确性。 如果变量X定义为字节变量,等价类(3,+∞)应进一步划分为[4,252]、[254,255]两个等价类和253这个临界点。 对于有符号数U和无符号数V,设[U]补码=254,[V]二进制=254,则有:U<-3,V>-3(因为[-3]补码=253)。如果程序中进行X:-3比较时混淆了两种不同类型数据的不同判断方法,使用X=254或者X=-2进行测试就能发现存在的错误。

健壮性测试: (1)超出原规定范围的数据。如原题规定X为字节数据,可以通过输入(-∞,-129),(+128,+∞)之间的数据进行测试。 (2)输入非数字,或者“十六进制”数字进行测试。 (3)输入“空”,即直接输入回车进行测试。

4.4.4 程序逻辑错误的调试 静态调试 程序员对照着源程序,人工模仿计算机进行处理。对每一组输入,仿照CPU的处理方法,一条一条指令地“执行”,确定程序执行的路线是否正确,验证每一个阶段的处理结果是否正确。 经验表明,静态调试可以发现70%以上的程序错误,是一种效率很高的调试方法,应该加以提倡。而且,通过认真地阅读源程序,还可能发现一些在测试中尚未发现的错误,发现程序中可以进一步“优化”的地方。

动态调试 在计算机上运行这个程序。主要着眼于两个方面: (1)  程序流程正确性:对于一组输入,检查程序是否执行了正确的流程。对于选择结构程序来说,程序是否按照预定的路线执行?对于循环结构程序,程序是否如设计的那样,进入了循环?循环次数是否正确?有无提前退出循环? 测试“程序流程正确性”的主要方法是跟踪程序的执行过程: “单步”执行程序 设置“断点”,分段执行程序

在调试程序“TD”中,跟踪命令主要有: F7:单步执行,遇到CALL,INT指令则进入子程序继续调试;

(2) 程序处理正确性:对于一组输入,是否进行了正确的计算,进行了正确的响应? (2) 程序处理正确性:对于一组输入,是否进行了正确的计算,进行了正确的响应? 测试“程序处理正确性”的主要方法是对每一阶段/每条指令的执行结果进行验证,判断这一阶段/这条指令执行的正确性。“执行结果”包括程序/指令执行后相关的寄存器、存储器、标志位的值。

[例4-19]假设为计算如下分段函数: 已编制汇编语言源程序如下: INCLUDE YLIB.H ; 1 ; 2 DATA SEGMENT ; 3 MESS1 DB 0DH, 0AH,'Input a number X: $' ; 4 MESS2 DB 0DH, 0AH,'The value of Y = $' ; 5 DATA ENDS ; 6 ; 7

CODE SEGMENT ; 8 ASSUME CS: CODE,DS: DATA ; 9 START:MOV AX, DATA ; 10 MOV DS, AX ; 11 LEA DX, MESS1 ; 12 CALL READINT ; 13 CMP AX, 3 ; 14 JAE GREAT ; 15 CMP AX, -3 ; 16 JAE LESS&EQU ; 17 GREAT: MOV AX,6 ; 18 ; 19

LESS&EQU:MOV BX, AX ; 20 SHL AX,1 ; 21 ADD AX,BX ; 22 SUB AX, 5 ; 23 DISP:LEA DX, MESS2 ; 24 CALL WRITEINT ; 25 MOV AX, 4C00H ; 26 INT 21H ; 27 CODE ENDS ; 28 END START ; 29

第一次测试的结果: 序号 输入 预定输出值 实际输出值 测试结果 1 4 6 13 错误 2 3 -2 -11 -3 -14 5 -4   对程序进行跟踪,输入X=4,程序流程为:……,12(√),13(√),14(√),15(√),18(√),20(×)。 执行第18行“MOV AX, 6”之后,对Y的计算已经结束,程序应转向标号“DISP”而不是进入第20行“GREAT”。 将第19行改为“JMP DISP”,对源程序重新汇编连接 。

第2次测试结果 序号 输入 预定输出 实际输出 测试结果 1 4 6 正确 2 3 错误 -2 -11 -3 -14 5 -4 再次对程序进行跟踪,输入X=3,程序流程为:……,13(√),14(√),15(√),18(×)。X=3,应归属于|X|≤3一类,进入18行(GREAT)是错误的。 将第15行改为“JG GREAT”,对源程序重新汇编连接。

第3次测试结果 序号 输入 预定输出 实际输出 测试结果 1 4 6 正确 2 3 错误 -2 -11 -3 -14 5 -4 输入X=3,程序流程为:……,13(√),14(√),15(√),16(√),17(√),18(×)。X=3,X≯3,进入16,17行是正确的,但X=3>-3,进入18行是错误的。 将第17行改为“JGE LESS&EQU”,对源程序重新汇编连接。

第4次测试结果 序号 输入 预定输出 实际输出值 测试结果 1 4 6 正确 2 3 -2 -11 -3 -14 5 -4 至此,对选定的测试数据运行正确。可以初步认为,程序基本能够完成预定的功能。 但是,任何测试方案都难以覆盖所有的输入可能,测试方案也有优劣之分。所以,通过了测试,不等于程序已经完全正确。 可以发现,上述错误大多数都可以通过“静态调试”来纠正。

习 题 四 4.1 什么是“三种基本结构”?解释“基本”两个字在其中的含义。 4.2 什么叫做“控制转移指令”?它和数据传送、运算指令有什么 区别?它是怎样实现它的功能的? 4.3 指令“JMP DI”和“JMP WOR PTR [DI]”作用有什么不同? 请说明。 4.4 什么是“近程”转移?什么是“远程”转移?它们的实现方法有 什么不同? 4.5 已知(AX)= 836BH,分别执行“CMP AX, X”后,标志 位ZF、CF、OF、SF各是什么? (1)X=3000H (2)X=8000H (3)X=7FFFFH (4)X=0FFFFH (5)X=0

4.6 已知(AX)= 836BH,分别执行“TEST AX, X”后,标志位 ZF、CF、OF、SF各是什么? (1)X=0001H (2)X=8000H (3)X=0007H (4)X=0FFFFH (5)X=0 4.7 要求测试名为X的一个字节,如果X的第1,3位均为1,转移 到L1,如果只有一位为1,转移到L2,如果两位全为0,转 移到L3。写出对应的指令序列。 4.8 假设X和X+2字单元存放有双精度数P,Y和Y+2字单元存 放有双精度数Q,下面程序完成了什么工作? MOV DX, X+2 MOV AX, X ADD AX, X ADC DX, X+2

CMP DX, Y+2 JL L2 JG L1 CMP AX, Y JBE L2 L1: MOV Z, 1 JMP SHORT EXIT L2: MOV Z, 2 EXIT: …… 4.9 编写指令序列,将AX和BX中较大的绝对值存入AX,较 小的绝对值存入BX。

4.10 编写指令序列,比较AX、BX中的数的绝对值,绝对值较 大的数存入AX,绝对值较小的数存入BX。 4.11 编写指令序列,如果AL寄存器存放的是小写字母,把它 转换成大写字母,否则不改变AL内容。 4.12 计算分段函数: X的值从键盘输入,Y的值送显示器输出。 4.13 计算分段函数: A,B的值从键盘输入,Y的值送显示器输出(∧表示“并且”, ∨表示“或者”)。

4.14 编写程序,求10元素字数组LIST中绝对值最小的数,存入 MIN单元。 4.15 编写程序,求20元素无符号字数组ARRAY中最小的奇数, 存入ODD单元,如果不存在奇数,将ODD单元清零。 4.16 一个有符号字数组以0为结束标志,求这个数组的:最大值、 最小值、平均值。 4.17 数组SCORE中存有一个班级40名学生的英语课程成绩。按 照0~59,60~74,75~84,85~100统计各分数段人数,存入N0, N1, N2, N3变量内。 4.18 STRING是一个16个字符组成的字符串,RULE是一个字整 数。编写程序,测试STRING中的每一个字符,如果该字符 为数字字符,把RULE中对应位置1,否则置0。 4.19 S_ARRAY是一个5个字符串组成的字符串数组,每个字符串 由16个字符组成,S_RULE是一个5个元素的字数组。编写程 序,按照4.18题的规则,用S_RULE数组记录S_ARRAY数组 的特征。

4.20 编写程序,从键盘上输入一个无符号字整数,用“四进制” 格式输出它的值(也就是,每2位二进制看作一位四进制 数,使用数字0~3)。 4.21 编写程序,把一个30个元素的有符号字数组ARRAY按照 各元素的正负分别送入数组P和M,正数和零元素送P数组, 负数送M数组。 4.22 缓冲区BUFFER中存放有字符串,以00H为结束标志。编 写程序,把字符串中的大写字母转换成小写字母。 4.23 编写程序,从键盘上输入无符号字整数X,Y的值,进行 X+Y的运算,然后按以下格式显示运算结果和运算后对应 标志位的状态。 SUM=XXXX ZF=X,OF=X,SF=X,CF=X 4.24 编写程序,从键盘上输入一个字符串,统计其中数字字符, 小写字母,大写字母,空格的个数并显示。

4.25 编写程序,从键盘输入一个无符号字整数,判断它是否为 素数,输出判断结果。 4.26 编写程序,按学号(1~40)输入一个班的汇编语言考试成 绩,统计每个学生成绩在班内的排名,按学号顺序输出这 个排名。(提示:排名等于成绩高于他/她的人数加1) 4.27 编写程序,读入20个数据,统计每个相同数据出现的次数。 4.28 编写程序,打印九九乘法表。 4.29编写程序,显示1000以内的所有素数。 4.30 编写程序,输入N,计算:S=1*2+2*3+……+(N-1)*N 4.31 编写程序,输入N,输出如下矩阵(设N=5) 1 1 1 1 1 2 2 2 2 1 3 3 3 2 1 4 4 3 2 1 5 4 3 2 1