Download presentation
Presentation is loading. Please wait.
1
第四章 汇编语言 程序设计 任课教师:王晓甜
2
本章要点 1 2 3 4 5 6 汇编语言程序设计基础 源程序的汇编、链接与调试 分支程序的设计 循环程序的设计 子程序的设计
综合程序的设计 6
3
1.汇编语言程序设计基础 算法 若题目涉及到某种运算,则必须写出适合程序设计的正确算法,若题目要完成的功能未涉及到运算,也要写出编程思想。
所谓算法,简单地说就是计算机能够实现的有限的解题步骤。我们知道,计算机只能进行最基本的算术运算和逻辑运算,要完成较为复杂的运算和控制操作,必须选择合适的算法,这是正确编程的基础。 若题目涉及到某种运算,则必须写出适合程序设计的正确算法,若题目要完成的功能未涉及到运算,也要写出编程思想。
4
1.汇编语言程序设计基础 4.4.1 程序设计的基本过程 一、程序设计的一般步骤 汇编语言程序设计基本上与高级语言程序设计一样,一般步骤:
程序设计的基本过程 一、程序设计的一般步骤 汇编语言程序设计基本上与高级语言程序设计一样,一般步骤: 1.分析问题并抽象出数学模型。 2.确定最佳算法。 3.画出程序结构框图和流程图。 4.合理分配内存工作单元和寄存器,并了解I/O接口地址。 5.编程并调试。(有时需要用注释行说明程序,便于阅读和修改。) 二、评价程序质量的标准 1.合理组织数据,发挥存贮器、Reg的作用。 2.程序逻辑结构好,便于二次开发。 3.可读性强。 4.高可靠性和可维护性。 5.效率高(代码少)。 模块化设计方法 “自顶向下,逐步细化” 结构化编码方法 顺序、分支、循环三种基本结构
5
编写程序 1.汇编语言程序设计基础 (1)必须详细了解CPU的编程模型、指令系统、寻址方式及相关伪指令;
采用汇编语言编写程序应注意以下几个问题: (1)必须详细了解CPU的编程模型、指令系统、寻址方式及相关伪指令; (2)必须进行存储空间和工作单元的合理分配; (3)多次使用的程序段可采用子程序或宏指令; (4)尽可能用标号或变量来代替绝对地址和常数;
6
§1.1 汇编语言基本元素
7
1.1.1 汇编语言的语句格式 由汇编语言编写的源程序是由许多语句(也可称为汇编指令)组成的。每个语句由1~4个部分组成,其格式是: [标识符] 指令助记符 [操作数][;注解] 其中用方括号括起来的部分,可以有也可以没有。每部分之间用空格(至少一个)分开,一行最多可有132个字符。
8
数字不能作标识符的第一个字符,而. 仅能作标识符的第一个字符,标识符最长为31个字符。
(1)标识符:给指令或某一存储单元地址所起的名字。 标识符由下列字符组成:(以字母或圆点开头) 字母: A~Z, a~z; 数字: 0~9; 特殊字符: ? _ $ 数字不能作标识符的第一个字符,而. 仅能作标识符的第一个字符,标识符最长为31个字符。 标识符后跟冒号时表示标号,代表该行指令的起始地址, 标号可以被转移、调用指令直接引用。 标识符后不带冒号时表示变量。 伪指令前的标识符不加冒号。
9
表示不同操作的指令,可以是8086/8088的指令助记符,也可以是伪指令。
(2)指令助记符 表示不同操作的指令,可以是8086/8088的指令助记符,也可以是伪指令。 (3)操作数 是指令执行的对象。依指令的要求,可能有一个、两个、没有或者多个。 例如: RET ;无操作数 COUNT: INC CX ;一个操作数 MOV CX,DI ;两个操作数 ADD AX,[BP十4] ;第二个操作数为表达式 (4)注释 该项可有可无,是为源程序所加的注解,用于提高程序的可读性。
10
1.1.2 汇编语言运算符 汇编语言运算符:是汇编程序在汇编时计算的,与运算指令不同,指令是在程序运行时计算的。
1、算术运算符、逻辑运算符、关系运算符 如:+、-、×、 / 、 AND、OR、LT等 MOV AX, [DI+BX] ADD AX, FIRST+1 MOV AX, ((choice LT 20) AND 5) OR ((choice GE 20) AND 6 )
11
注意: 算术运算符总可以用于数字操作,其结果也是数字的。当应用于存储器操作数时,只有+, -运算符有意义;
逻辑运算符的操作数也必须是数字,存储器操作数不能进行逻辑运算; 关系运算符连接的两个操作数,必须都是数字的或是在同一段内的存储器地址。
12
2、取值运算符 1) $运算符 $:当前地址偏移量的值 BLOCK DB ‘HELLO!’ NUM EQU $-BLOCK ;NUM为 6
1) $运算符 $:当前地址偏移量的值 BLOCK ‘H’ ‘E’ ‘L’ ‘O’ ‘!’ BLOCK DB ‘HELLO!’ NUM EQU $-BLOCK ;NUM为 6 $
13
SEG 和OFFSET SEG: 求标号或变量的段地址 OFFSET: 求标号或变量的偏移地址 例如,定义: SLOT DW 25
则:MOV AX,SLOT; 从SLOT地址中取一个字送入AX MOV AX,SEG SLOT; 将SLOT的段地址送入AX MOV AX,OFFSET SLOT; 将SLOT的段内偏移地址送AX
14
3) TYPE 返回标号或存储器操作数的类型值 对存储器操作数:表示占用的字节数 对标号:表示过程或指令地址的调用类型
表 存储器操作数的类型属性及返回值 字节 字 双字 NEAR FAR - -2
15
4) LENGTH 和 SIZE(对用DUP定义数据的情况下)
返回一个与存储器操作数相联系的基本数据个数, SIZE: 返回一个为存储器操作数分配的字节数 关系:SIZE=LENGTH × TYPE 例如:若 MULT-WORD DW 50 DUP(0) 则 LENGTH MULT-WORD=50 SIZE MULT-WORD=100 TYPE MULT-WORD=2
16
3、属性运算符 1) PTR 定义操作数为新的类型 一般格式:类型 PTR 操作数
用来给指令中的操作数指定一个临时的属性,而暂时忽略操作数定义时的属性。 1) PTR 定义操作数为新的类型 一般格式:类型 PTR 操作数 功能:建立一个存储器操作数,它与其后的存储器操作数有相同的段地址和偏移地址,但有不同的类型。 F2 DW H MOV AL, BYTE PTR F2 MOV [BX], 3 MOV BYTE PTR [BX], 3 ; AL: 56H ; 错,类型不明确 ; 字节传输
17
1.1.3 表达式 是由运算符和操作数组成的序列,在汇编时产生一个确定的值。这个值可以仅表示一个常量,也可以表示一个存储单元的偏移地址,相应的表达式称为常量表达式和地址 表达式。
18
‘BD’ ‘This is a classroom.’
1、 常数 二进制(B),八进制(Q),十六进制(H),十进制(D)(默认),十进制浮点数,十六进制实数,字符和字符串 B Q H ‘BD’ ‘This is a classroom.’
19
2、常量操作数 常量操作数是一个数值操作数,一般是常量或者是表示常量的标识符。 如:COUNT EQU 10 NAME=‘J’ 可以为数字常量操作数或字符串常量操作数。前者可采用二进制、八进制、十进制或十六进制等进位计数形式;而后者所对应的常量值为相应字符的ASCII码。
20
3、存储器操作数 存储器操作数是一个地址操作数,代表一个存储单元的地址,通常以标识符的形式出现。 变量:代表的是某个数据在数据段、附加段或堆栈段中的地址。变量所对应的存储单元内容在程序的运行过程中是可以改变的。 标号:代表的是某条指令代码在代码段中的地址。标号通常作为转移指令或调用指令的目标操作数,在程序运行过程中不能改变。
21
存储器操作数有三个属性 段属性(SEG) : 所对应存储单元的段地址 偏移量属性(OFFSET) :所对于存储单元在所在段内的偏移地址(距段起点的字节数) 类型属性(TYPE) 变量的类型 是占用存储单元的字节数,分为: DB(1个字节) DW(2个字节) DD(4个字节) 标号的类型 则反映了相应存储单元地址在作为转移或调用指令的目标操作数时的寻址方式,可有两种情况,即NEAR和FAR。
22
DATA SEGMENT X DB 5, 4 Y DW 40H Z DD 2030H DATA ENDS X,Y,Z分别都有三个属性:
DS:0000H X Y X,Y,Z分别都有三个属性: 段地址,偏移地址和类型值, 这三个属性都有固定的值。 Z
23
4、常量表达式 由常量操作数及运算符构成,在汇编时产生一个常量。 如PORT、VAL十1、 OFFSET SUM、 SEG SUM、TYPE CYCLE等。
24
5、地址表达式 由存储器操作数与运算符构成,但由存储器操作数构成地址表达式时,必须有明确的物理意义。 例如 SUM+2、CYCLE-5 表达式SUM+2、CYCLE-5的值仍然是一个存储器操作数,该存储器操作数的段地址与类型属性分别与存储器操作数SUM及CYCLE相同,但偏移地址分别比SUM及CYCLE大2或小5。表达式是在汇编时计算的,而变量单元的内容在程序的运行过程中可以改变。
25
1.1.4 汇编语言程序汇编步骤
26
汇编 连接 .EXE .ASM .OBJ 宏汇编程序: MASM.EXE 连接程序: LINK.EXE 步骤:
编辑程序 汇编程序 目标程序 连接程序 执行程序 源程序 汇编 连接 .EXE .ASM .OBJ 宏汇编程序: MASM.EXE 连接程序: LINK.EXE 步骤: 1)编写源程序;2)汇编;3)连接;4)调试。
27
用户编写程序,程序在计算机中运行,计算机的控制由操作系统交给用户程序,运行用户程序,当用户程序运行结束后,应再将控制权交回操作系统,所以,在程序中应该有返回DOS的操作。在计算机中,返回DOS的操作由操作系统中的一个子程序来实现,用户使用时调用这个子程序即可。
28
每当一个用户的可执行文件.EXE装入内存后,存储器的分配情况如图:
00000H 系统占用 程序段前缀 用户数据区 用户堆栈区 用户代码段 …… 系统和ROM占用 DS,ES 100个字节 SS 用户程序空间 CS FFFFFH
29
无论用户程序有几段,也无论这些段的排列顺序如何,用户程序的代码前一定有100个字节的程序段前缀(Program Segment Prefix, 简称PSP),PSP给出了用户的可执行文件(.EXE)的若干控制信息。其中PSP的开始处(第1,2字节)有一条中断指令INT 20H的代码,通过它可以结束用户程序,返回操作系统。在用户程序执行完以后,通过执行该条指令就可以返回DOS。 系统占用 INT 20H 程序段前缀 用户数据区 用户堆栈区 用户代码段 …… 系统和ROM占用
30
如何使用户程序执行完后返回来执行这条指令?
首先将用户程序定义为一个远过程,当可执行文件装入内存后,DS,ES两个段寄存器被CPU自动设置为指向PSP的首址,所以一般程序的开始指令为: PUSH DS MOV AX, 0 PUSH AX 即将DS的内容和0000H压入堆栈,程序结束时的最后一条语句为RET,就把压入堆栈的PSP段的段地址和偏移地址0000H弹出并送入CS和IP,转而执行返回DOS的指令INT 20H。 系统占用 INT 20H 程序段前缀 用户数据区 用户堆栈区 用户代码段 …… RET 系统和ROM占用 DS,ES 00H PSPL PSPH 堆栈情况 SS CS
31
返回操作系统的另一个办法是通过系统调用(调用号是4CH)。这时, 在用户程序结束时,用下面两条指令:
MOV AH, 4CH INT 21H 即可实现返回DOS。
32
§1.2 伪指令
33
伪指令:伪指令不是CPU运行的指令,而是程序员给汇编程序下达的命令。是在汇编源程序期间由汇编程序执行的命令。
34
DB:定义字节,其后的每个操作数占有一个存储单元,连续存放;
1.2.1 数据定义伪指令 DB:定义字节,其后的每个操作数占有一个存储单元,连续存放; ‘O’ ‘N’ 03H 02H BUFFER BUFFER DB 2, 3 STRING DB ‘NO’ STRING DW:定义字,其后的每个操作数占有两个字节; BUF DW 2, 3, 5 05H 00H 03H 02H BUF DD: 定义双字,其后每个操作数占4个字节; 还有DQ(4个字长)、DT(10个字节长)
35
若仅保留单元,不初始化,用?代替初值; 若数据重复,用 n DUP( )代替,n为重复次数。 ARRAY DB DUP(?) 保留100个字节,首地址为ARRAY,不初始化,即100个字节内均为随机值 DATA1 DB DUP(‘AB’) 初始化200个字节,内有100个41H, 42H (即41H 42H 41H 42H 41H 42H……)
36
例:有如下数据定义伪指令: VAL DB 1,4 DUP (5, 2 DUP(FFH, 0 )) 则在VAL存储区前10个字节单元的数据是: 1, 5, FFH, 0, FFH, 0, 5, FFH, 0, FFH 若定义 DW 1, 2, 5 DUP(‘YES’,2 DUP(3)), 则在存储区的数据是什么?
37
1.2.2 符号定义伪指令 标识符 EQU 表达式 标识符 = 表达式
符号定义伪指令 标识符 EQU 表达式 标识符 = 表达式 给标识符定义一个值或其他符号名或一条可执行语句,汇编时,凡是出现该标识符的地方就用定义的数据替代。 TIMES EQU 50 BUF DB TIMES DUP(?) 等效于: BUF DB 50 DUP(?) BETA = TIMES-2 BETA = TIMES+5 ;重新赋值 注意:用EQU赋值的名字不能重新赋值,需用PURGE释放后重新定义。如:PURGE TIMES TIMES EQU 100
38
数据段 L为常量,不占空间,值为2 03H DATA SEGMENT 00H A DW 3 04H DS:0000H B DW 4
L EQU B-A DATA ENDS 03H 00H 04H DS:0000H 数据段 L为常量,不占空间,值为2
39
段名 SEGMENT [定位类型] [组合类型] [类别]
段定义伪指令SEGMENT和ENDS 一般的源程序分为4个段:代码段Code、数据段Data、堆栈段Stack、附加段Extra。 各个段从段定义语句开始,到段结束语句ENDS结束。 段名 SEGMENT [定位类型] [组合类型] [类别] …… 段体 用户指定 可任选定义 段名 ENDS
40
1)段名必须是合法的标识符。 2)定位类型: 表示本段起始地址位于何处 内存可以看成是一本书,将其分成页,段,字,和字节。
00000H 2)定位类型: 表示本段起始地址位于何处 第一页 (256个地址) 内存可以看成是一本书,将其分成页,段,字,和字节。 每256个地址为一页(PAGE),每页的起始地址为二进制: **** **** **** 16进制:***00H 000FFH 00100H 第二页 (256个地址) 001FFH 00200H
41
段(节)是默认的定位类型。 每16个地址为一段(PARA), 每段的起始地址为二进制: **** **** **** **** 0000
16进制:****0H ***00H 第一段 (16个地址) ***0FH ***10H 段(节)是默认的定位类型。 第二段 (16个地址) ***1FH ***20H
42
每2个地址为一个字(WORD),每个字的起始地址为偶数;
最基本的类型是字节(BYTE),每个字节只包含一个地址,可以是内存的任何空间。 定位类型表示所定义的段存放在内存空间时,段首地址对内存空间的要求,即段起始点是放在一页的起点上(PAGE)还是一段的起点上(PARA)等,如果不定义定位类型,编译程序将默认其为段类型,即将段首地址放在从****0H开始的内存空间。 BYTE: 表示本段起始单元可以从任一地址开始; WORD: 表示本段起始单元从一个偶地址开始; PARA: 表示本段起始单元从一个段的边界开始(默认); PAGE: 表示本段起始单元从一个页的边界开始。
43
3)组合类型 告诉汇编程序,所定义的段与其他段的关系,即将该段存放内存时,是否将该段与其他段在物理上或逻辑上放在一起。 NONE: 表示本段与其他段不发生任何关系,该段有自己的段基址,是默认的组合关系。 PUBLIC:在满足定位类型的前提下与其他模块的同名段连接在一起,形成一个新的逻辑段,共用一个段基址。
44
COMMON: 表示产生一个覆盖段。连接时,把本段与其他也用COMMON说明的同名段置成相同的起始地址,重叠在一起,共享相同的存储区,其段长度由最长的段确定。
STACK: 在每个汇编程序中,只能必须有一个堆栈段,连接时,将本段与其他也用STACK说明的同名段连接成一个连续的STACK段,编译程序自动初始化SS和SP寄存器,使SS的内容为该连续段的段基址,SP指向堆栈底部加1的存储单元。
45
MEMORY: 表示本段在存储器中应定位在所有其他段的最高地址。
AT<表达式>: 表示本段从表达式指定的地址处开始装入,这样,在程序中用户就可以直接定义段地址,这种方式不适用于代码段。
46
4) 类别 是用单引号括起来的字符串,以表明该段的类别,如代码段(CODE)、数据段(DATA)、堆栈段(STACK)等。当然也允许用户在类别中用其他的名,这样进行连接时,连接程序便将同类别的段(但不一定同名)放在连续的存储区内。
47
ASSUME 段寄存器: 段名[,段寄存器: 段名,…]
一般格式: ASSUME 段寄存器: 段名[,段寄存器: 段名,…] 功能:通知汇编程序,哪一个段寄存器是该段的段寄存器,以便对使用变量或标号的指令汇编出正确的目的代码。 例如,CODE SEGMENT ASSUME CS:CODE,DS:DATA,SS:STACK
48
MOV DS,AX ;AX内容送DS,DS才有实际段值
注意: 当程序运行时,由于DOS的装入程序负责把CS初始化成正确的代码段地址,SS初始化为正确的堆栈段地址,因此用户在程序中就不必设置。但是,在装入程序中DS寄存器由于被用作其它用途,因此,在用户程序中必须用两条指令对DS进行初始化,以装入用户的数据段段地址。当使用附加段时,也要用MOV指令给ES赋段地址。 例如,CODE SEGMENT ASSUME CS:CODE,DS:DATA,SS:STACK MOV AX,DATA ;DATA段值送AX MOV DS,AX ;AX内容送DS,DS才有实际段值 CODE ENDS
49
在程序设计中,可将具有一定功能的程序段看成为一个过程(相当于一个子程序),它可以被别的程序调用。 要求先定义后使用。
过程定义伪指令PROC和ENDP 在程序设计中,可将具有一定功能的程序段看成为一个过程(相当于一个子程序),它可以被别的程序调用。 要求先定义后使用。 一个过程由伪指令PROC和ENDP来定义,其格式为: 过程名是为过程所起的名称,不能省略 过程名 PROC [类型] 过程体 RET 过程名 ENDP 注意:PROC和ENDP要成对出现。 类型由FAR(远过程,为段间调用)和NEAR(近过程,为段内调用)来确定,如果缺省类型,则该过程就默认为近过程。 过程体内至少有一条RET指令
50
一个码段中可以包含一个或许多过程。过程可以嵌套调用,可以递归调用,但不可以嵌套定义。
MYCODE SEGMENT ASSUME CS: MYCODE SUB1 PROC FAR …… RET SUB1 ENDP SUB2 PROC NEAR SUB2 ENDP CALL SUB2 MYCODE ENDS FAR: 该过程为远过程,调用该过程时为段间调用,即CS和IP均要重新赋值; NEAR: 该过程为近过程,调用该过程时为段内调用,只修改IP。(默认) CALL: 调用过程SUB2,到此处才真正去执行子程序。
51
例:延时100ms的子程序,编程定义如下: DELAY PROC MOV BL, 10 AGAIN: MOV CX, 2801H WAIT1: LOOP WAIT1 DEC BL JNZ AGAIN RET DELAY ENDP 任何一个过程都要求先定义,后调用。调用时才真正执行该过程。一个过程产生一段目标代码。
52
宏指令 在汇编语言书写的源程序中,有的程序段要多次使用,为了简化书写,该程序段可以用一条特殊的指令来代替,这个特殊的指令就是宏指令。 宏指令只是为了方便书写,当汇编程序汇编生成目标代码时,在引用宏指令处仍会产生原来程序段应生成的目标代码,引用一次生成一次。
53
宏指令名 MACRO <形参列表> 汇编程序段(宏体) ENDM
1) 宏指令定义格式: 宏指令名 MACRO <形参列表> 汇编程序段(宏体) ENDM MACRO 与 ENDM必须成对出现,先定义后引用 宏名 在程序中引用宏指令如下: SHIFT MACRO MOV CL, 4 SAL AL, CL ENDM IN AL, 5FH SHIFT OUT 5FH, AL 程序段: 将AL左移4位(乘16)
54
宏指令也可以接收参数,如对X左移Y位。 BXX 4Y 实参形参一一对应 功能:BX的值左移4位。 形参列表
SHIFT MACRO X, Y MOV CL, Y SAL X, CL ENDM BXX 4Y 实参形参一一对应 在程序中引用宏指令如下: MOV BX, WORD PTR BUF SHIFT BX, 4 MOV WORD PTR BUF , BX 功能:BX的值左移4位。
55
格式:ORG 表达式 1.2.7 定位伪指令 功能:指定在其后的指令或数据存放的偏移地址。 CODE SEGMENT ORG 0100H
定位伪指令 格式:ORG 表达式 功能:指定在其后的指令或数据存放的偏移地址。 CODE SEGMENT ORG H MOV AX, 0 CODE ENDS DATA SEGMENT ORG H NUM DB 0,5 DATA ENDS 指定MOV指令放在代码段中偏移地址为0100H开始的位置。 指定NUM指向数据段中偏移地址为2000H开始的位置。
56
一般格式:END 表达式 1.2.8 汇编结束伪指令END 表达式为可执行程序运行的起始位置。一般是一个标号。
57
1.汇编语言程序设计基础 汇编语言源程序的完整结构
58
完整结构1 数据段 堆栈段(可略) 分配段寄存器 设置DS段寄存器内容 首指令位置 返回DOS 源程序结束,第一条指令的地址
DATA SEGMENT X DB 3 Y DB 5 DATA ENDS STACK SEGMENT STACK ‘STACK’ DB DUP(?) STACK ENDS CODE SEGMENT ASSUME CS:CODE, DS: DATA, SS:STACK BEGIN: MOV AX, DATA MOV DS, AX …… MOV AH,4CH INT 21H CODE ENDS END BEGIN 数据段 堆栈段(可略) 分配段寄存器 设置DS段寄存器内容 首指令位置 返回DOS 源程序结束,第一条指令的地址
59
完整结构2 数据段 堆栈段(可略) 分配段寄存器 将PSP首址压栈,以便返回DOS。 主过程名 设置DS段寄存器内容 返回DOS
DATA SEGMENT X DB 3 Y DB 5 DATA ENDS STACK SEGMENT STACK ‘STACK’ DB DUP(?) STACK ENDS CODE SEGMENT ASSUME CS:CODE, DS: DATA, SS:STACK BEGIN PROC FAR PUSH DS XOR AX, AX PUSH AX MOV AX, DATA MOV DS, AX …… RET BEGIN ENDP CODE ENDS END BEGIN 数据段 堆栈段(可略) 分配段寄存器 将PSP首址压栈,以便返回DOS。 主过程名 设置DS段寄存器内容 返回DOS 源程序结束,第一条指令的地址
60
本章要点 1 2 3 4 1 3 汇编语言程序设计基础 源程序的汇编、链接与调试 分支程序的设计 循环程序的设计 子程序的设计
综合程序的设计 3
61
2.源程序的汇编、连接与调试 汇编语言的基本编程过程可以总结成表4.2所示 步骤 输入 涉及的程序 输出 1.编辑源程序 键盘 “记事本”等
myfile.asm 2.汇编源程序 MASM 或 TASM myfile.obj 3.连接程序 LINK 或 TLINK myfile.exe
62
2.源程序的汇编、连接与调试 一、编辑源程序 利用编辑程序“记事本”等编写源程序,其规则应该遵循8086 CPU的指令系统的要求,源程序名的扩展名必须为ASM。 例如myfile.asm,下面以此为例加以说明。
63
2.源程序的汇编、连接与调试 二、汇编源程序 汇编过程是利用汇编程序MASM对源程序文件进行汇编. MASM功能:
找出源程序中指令格式的错误、标号变量定义错误(存在没有定义或重复定义标号变量); 生成三个文件:目标文件(myfile.obj,必须产生)、列表文件(myfile.lst)和交叉索引文件(myfile.crf)。 目标文件为指令、伪指令编译后的目标代码文件; 列表文件中列出了程序代码、偏移地址以及出错信息,可以方便地分页打印装订; 交叉索引文件列出了程序中所定义地所有标识符和标号及其引用情况。
64
2.源程序的汇编、连接与调试 汇编程序一般采用MASM,其使用格式为: MASM source,object,list,crossref
在实际使用的简略方式: MASM myfile; ;表示只生成myfile.obj MASM myfile ;按屏幕提示进行操作 MASM myfile,,list; ;表示要生成myfile.obj和myfile.lst
65
2.源程序的汇编、连接与调试 三、连接程序 连接程序LINK将目标程序连接成可执行文件。
两个输入文件:目标文件(.obj)和库文件(.lib)(汇编语言程序连接时不需要库文件,高级语言程序连接时需要相应的库文件; 两个输出文件:可执行文件(.exe)和内存分配文件(.map)。 连接命令的常用格式有: LINK object; ;对目标文件进行连接,并生成二进制代码文件(.exe) LINK object ;没有命令末的分号,这时可按屏幕提示进行操作
66
2.源程序的汇编、连接与调试 四、程序调试 源程序编写后,通过汇编和连接后,就得到了可以在计算机系统中直接执行的二进制代码文件,但程序执行的结果是否正确则无法判断。利用MASM对源程序汇编时可以检测出程序的语法错误、指令用法错误,程序执行的情况需要通过程序调试来完成。 汇编语言程序的调试可以借助于专门的调试工具软件DEBUG来实现。
67
2.源程序的汇编、连接与调试 DEBUG提供了强大的调试功能,主要有: 显示、修改寄存器和内存单元的内容(R命令)
单(多)条跟踪执行(单步执行)(T、P命令) 直接输入汇编语句(A命令) 显示并修改内存单元的内容(D、E、F命令) 读磁盘扇区 读/写文件
68
2.源程序的汇编、连接与调试 DEBUG软件的常用命令 1.启动DEBUG DEBUG [d:][path][文件名.扩展名]
启动DEBUG软件,并加载(装入)指定的文件,在缺省文件名时,可直接DEBUG状态,其提示符为“-”。上式中,“d:”为磁盘符号,表示可以指定不同的驱动器;“path”表示路径名;文件必须是包含扩展名的完整形式,在调试程序时,应该是.exe文件。 例如要对myfile.exe进行调试,则可以输入: DEBUG myfile.exe
69
2.源程序的汇编、连接与调试 2. 显示各个寄存器的内容
在DEBUG状态下,输入命令R,可以显示出所有寄存器的当前内容,如图4.2 所示。通用寄存器和段寄存器的内容一目了然,第二行的右端给出出了PSW中的8个状态标志位,它们是采用字母来表示其意义的,依次分别为:溢出标志(OF)、方向标志(DF)、中断允许标志(IF)、符号标志(SF)、零标志(ZF)、半进位标志(AF)、奇偶标志(PF)和进位标志(CF),其符号含义如表4.3所示。图4.2中,最后一行表示所加载程序的第一条即将执行的指令。 标志位的符号含义
70
2.源程序的汇编、连接与调试 标志位名称 标志位为“1”的符号 标志位为“0”的符号 溢出标志(OF) OV NV 方向标志(DF) DN
UP 中断允许标志(IF) EI DI 符号标志(SF) NG PL 零标志(ZF) ZR NZ 半进位标志(AF) AC NA 奇偶标志(PF) PE PO 进位标志(CF) CY NC
71
2.源程序的汇编、连接与调试 3. 显示并修改某个寄存器的内容 当要显示并修改AX寄存器的内容时,也可以采用R命令,如: R AX
这时DEBUG会显示出AX的当前内容“AX 0000”,并提示用户输入更改值,当不想修改时,可以直接按回车键。
72
2.源程序的汇编、连接与调试 4. 显示修改标志寄存器 利用R命令还可以修改个别标志位,例如输入: R F
则会显示出当前的标志位状态“NV UP EI PL NZ NA PO NC - ”,并等待用户输入更改值,当需要更改IF和CF时,可以直接输入“DICY”,这时可以将IF位清0、CF位置1,而且输入顺序可以不按标志位的次序。
73
2.源程序的汇编、连接与调试 5. 反汇编目的代码 可以利用U命令反汇编出内存中的二进制代码,即以汇编语言指令形式表示出二进制代码。
(1)U ;从当前CS:IP地址开始反汇编,每次对约32个字节的代码进行反汇编,下次U命令会从本次结束位置开始反汇编。 (2)U addr ;从指定地址(addr)开始进行反汇编。 (3)U addr1,addr2 ;从地址1(addr1)反汇编到地址2(addr2)
74
2.源程序的汇编、连接与调试 6. 设置断点并执行程序 可以利用G命令实现程序的分段执行。G命令主要有四种格式:
(1)G ;从当前地址(CS:IP)开始执行程序,直到程序结束。 (2)G=addr ;从指定地址(addr)开始执行程序,直到程序结束。 (3)G=addr1,addr2 ;从地址1(addr1)执行到地址2(addr2),实际上在所指定的地址2处设置了一个断点,这样可以使程序得以分段执行。 (4)G addr ;从当前地址CS:IP执行到指定的地址(addr),即在addr处设置了断点。
75
2.源程序的汇编、连接与调试 7. 显示并修改内存单元的内容 D命令用于显示内存(存储)单元的内容; E命令用于显示并修改存储单元的内容;
F命令用于给一块存储区域置入同一个值。 D命令的常用格式有三种:
76
2.源程序的汇编、连接与调试 D命令的常用格式有三种:
(1)D [Daddr:]Offset ;从指定地址开始显示128个字节单元的内容,Daddr指定段地址,缺省时为DS的内容,它可以直接指定段地址值,也可以为DS、ES、CS和SS;Offset用于指定段内偏移地址。 (2)D ;继续上一次显示的内存位置开始显示128个字节单元的内容,如果是第一次显示,则从DS:0位置开始显示。 (3)D [Daddr:]Offset1 Offset2 ;从指定段的地址1(Offset1)显示到地址2(Offset2)。
77
2.源程序的汇编、连接与调试 E命令的常用格式有两种:
(1)E [Daddr:]Offset ;从指定地址开始显示一个字节单元的内容,用户可以通过输入新值进行修改,按空格键表示确认修改,这时会自动显示下一个单元的内容。如果不修改该单元的内容,可以直接按空格键。按回车键表示E命令结束。 (2)E [Daddr:]Offset Expression ; 直接修改指定单元的内容,Expression为多个字节内容构成的表达式,字节之间用空格间隔。例如E 表示将DS:100H开始的5个字节单元的内容改成“10H 20H 30H 40H 50H”。应该注意,在DEBUG下的所有数值只能是十六进制数。
78
2.源程序的汇编、连接与调试 F命令的常用格式有两种:
(1)F [Daddr:]Offset1 Offset2 Expression ;以表达式(Expression)的值依次填入从地址1(Offset1)到地址2(Offset2)的所有单元,例如F AA表示将DS:100H到200H的所有单元间隔写入55H和AAH。 (2)F [Daddr:]Offset L length Expression ;以表达式(Expression)的值依次填入从地址(Offset)开始、长度为length中的所有单元,例如F100L AA表示将DS:100H到200H的所有单元间隔写入55H和AAH。
79
2.源程序的汇编、连接与调试 8.内存单元内容的传送 在DEBUG下,利用M命令可以将一块区域的内容传送到另一个位置,它常用的有两种格式:
(1)M [Daddr:]Offset1 Offset2 Offset3 ;表示将从地址1(Offset1)到地址2(Offset2)的所有单元的内容传送到地址Offset3开始的单元中,例如M 表示将DS:100H到200H的所有单元传送到300H开始的单元中。 (2)M [Daddr:]Offset1 L length Offset2 ;将从地址1(Offset1)开始、长度为length中的所有单元的内容传送到地址Offset2开始的单元中。
80
2.源程序的汇编、连接与调试 9.程序的单步执行
在DEBUG下,可以利用T命令或P命令单步执行程序,它们不带任何参数,每次都会执行一条指令,同时会显示出所有寄存器的内容(与R命令显示的形式一致)。 但T命令与P命令是有区别的,T命令每次执行汇编语言的一条指令,而P命令每次执行汇编语言的一条语句,对于像CALL sub、INT n这样的语句,执行T指令表示转向子程序或中断服务子程序,而执行P命令时,则表示执行完整个子程序或中断服务子程序,因此,在遇到DOS中断调用指令时,经常采用P命令,以避免程序转入DOS本身的中断服务子程序。
81
2.源程序的汇编、连接与调试 10.输入汇编语言指令 在DEBUG下,可以利用A命令直接输入汇编语言的指令,常用格式有两种:
(1)A [Daddr:]Offset ;从指定地址Offset开始输入汇编语言指令,每输入一条指令,DEBUG软件会自动编译该指令,并生成相应的二进制代码,同时计算出下一条指令的存放地址,用户可以继续输入汇编语言指令。如果按回车键则可以结束A命令。 (2)A ;从上一次A命令结束的地址进行输入汇编语言指令,如果是第一次使用,则默认从CS:IP地址开始输入汇编语言指令。
82
2.源程序的汇编、连接与调试 11.文件装入 在DEBUG下,可以重新装入文件,这时需要分两步:先指定文件名(N命令),然后装入文件(L命令)。 N命令的格式为: N [path] file ;指定file为文件名,可以包含扩展名。 L命令的常用格式有两种: (1)L [Daddr:]Offset ;将指定文件装入到从地址Offset开始的单元中。 (2)L ;默认将文件装入到从CS:100H开始的单元中。 文件装入后,其装入的字节数存放在由BX和CX构成的32位寄存器中,BX的内容为高16位,CX的内容为低16位。
83
2.源程序的汇编、连接与调试 12.保存文件 在DEBUG下,可以利用N命令和W命令将指定区域存储单元的内容保存到文件中,这时需要分三步:指定文件名(N命令)、指定存储的长度(修改BX和CX的内容)、保存文件(W命令)。 W命令的格式为: W [Daddr:]Offset ;将从地址Offset开始、长度为(BX:CX)的存储内容保存到指定文件。应该这样,必须修改BX和CX的内容,以确保正确的保存。
84
2.源程序的汇编、连接与调试 13.退出DEBUG软件 在DEBUG下,输入Q可以退出DEBUG软件。
85
2.源程序的汇编、连接与调试 命令名称 功 能 R 显示并修改寄存器的内容 T 单步执行汇编语言指令 D 显示存储单元的内容 P
单步执行汇编语言语句 E 显示并修改存储单元内容 A 输入汇编语言指令 F 填充存储单元内容 N 指定文件名 M 传送存储单元内容 L 装入文件 U 反汇编指令代码 W 保存文件 G (分段)执行程序 Q 退出DEBUG软件
86
Thank You !
87
第四章 汇编语言 程序设计 任课教师:王晓甜
88
本章要点 1 2 3 4 5 6 汇编语言程序设计基础 源程序的汇编、链接与调试 分支程序的设计 循环程序的设计 子程序的设计
综合程序的设计 6
89
顺序程序的设计 顺序程序设计是没有分支,没有循环的直线运行程序,程序执行按照IP内容自动增加的顺序进行。
【例4-1】编程将内存数据段字节单元INDAT存放的一个数n(假设0≤ n ≤9 ),以十进制形式在屏幕上显示出来。 例如,若INSTR单元存放的是数8,则在屏幕上显示:8D。
90
顺序程序的设计 DATA SEGMENT ;数据段定义 INDAT DB 8 DATA ENDS CODE SEGMENT ;代码段定义
ASSUME CS:CODE,DS:DATA START: MOV AX,DATA MOV DS,AX ;初始化DS MOV DL,INDAT OR DL,30H MOV AH, 2 INT 21H MOV DL,'D' MOV AH,2 MOV AH,4CH CODE ENDS END START
91
分支程序的设计 分支结构程序利用条件转移指令,使程序执行完某条指令后,根据指令执行后状态标志的情况选择要执行哪个程序段。 分支结构程序的指令执行顺序与指令的存储顺序不一致。 转移指令JMP和JXX可以实现分支结构。 条件转移 注意:有些指令的执行对PSW的标志位没有影响,而且有条件转移指令的转移范围为-128~+127, 因此合理选择条件转移指令在分支程序中是至关重要的,也是正确程序设计的关键。 在分支程序设计中,要特别注意每个分支的完整性,分支中包含PUSH和POP指令时,应该确保每一条分支中PUSH和POP指令数的对等。
92
分支程序的设计 二条件跳转 常见普通形式(1) CMP AL, BL; 计算,设置标志位 JZ LABEL; 利用标志位跳转
JMP OVER; 标志位不生效,跳至结束 LABEL: MOV AH 00H ………….. OVER:……… 常见普通形式(2) LABEL: ………… DEC BL; 计算,设置标志位 JZ LABEL; 利用标志位跳转 OVER: ……………;标志位不生效,顺序向下执行 用跳转实现循环 二条件跳转
93
分支程序的设计 二条件跳转 常见普通形式 CMP AL, BL; 计算,设置标志位 JZ LABEL; 利用标志位跳转,执行条件A
MOV AH 0FFH 标志位不生效,执行条件B ………….. JMP OVER; 跳至结束 LABEL: MOV AH 00H …………… OVER: ……….
94
分支程序的设计 多条件跳转 常见普通形式 CMP AL, BL; 计算,设置标志位 JAE LAB1; 利用标志位跳转至LAB1
MOV AH, 0FFH 标志判断不生效,执行条件A JMP OVER; 跳至结束 LAB1: JZ LAB2; 利用标志位跳转至LAB2 MOV AH, 00H 标志判断不生效,执行条件B JMP OVER; 跳至结束 LAB2: MOV AH, 55H; 执行条件C OVER: ………. 多条件跳转
95
分支程序的设计 二条件跳转 例4.1 第三章作业3.10 (4) 测试BX中的位1和位2,当这两位同时为0时,将AL置0FFH,否则清零
例4.1 第三章作业3.10 (4) 测试BX中的位1和位2,当这两位同时为0时,将AL置0FFH,否则清零 TEST BX , 0006H JZ L1 XOR AL, AL JMP L2 L1: MOV AL, 0FFH L2: …. 二条件跳转
96
分支程序的设计 例4.2 字节型变量VAR1、VAR2和VAR3存放有3个无符号数,将其中的内容按从大到小重新排列。 解:经重新排列后,VAR1放最大值,VAR3放最小值。由于变量中存放的数据为无符号数,因此应该采用JA、JAE、JB、JBE等指令。 编程思路:通过在三个数中找出最大值,将它与VAR1单元进行交换;然后对剩余的两个数进行比较,将较大值存放在VAR2中。汇编语言程序如下: 二条件跳转
97
分支程序的设计 将较大的数放进AL 较小的数放进 VAR2 将较大的数放进AL 较小的数放进 VAR3 此时 AL 最大
比较剩下两个较小的, 并按顺序放置
98
分支程序的设计 将大值放入AL,继续与VAR3比 将大值放入AL,获得三中的最大值 MOV AL,VAR1 CMP AL,VAR2
JAE NO_CHG1 XCHG AL,VAR2 NO_CHG1: CMP AL,VAR3 JAE NO_CHG2 XCHG AL,VAR3 NO_CHG2: MOV VAR1,AL ;最大值保存到VAR1 MOV AL,VAR2 JAE NO_CHG3 MOV VAR2,AL ;次大值保存到VAR2 NO_CHG3: MOV AH,4CH ;返回DOS操作系统 INT 21H CODE ENDS END START 将大值放入AL,继续与VAR3比 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT VAR1 DB 46H VAR2 DB 15H VAR3 DB 0A2H DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP 将大值放入AL,获得三中的最大值
99
分支程序的设计 例4.3编程求分段函数Y的值。已知变量X为16位带符号数,分段函数的值要求保存到字单元Y中。函数定义如下: 多条件跳转
100
分支程序的设计 本例实现的是多分支结构。 设计多分支结构程序时,应注意: FINISH: MOV AH,4CH INT 21H
DATA SEGMENT ;数据段定义 X DW -128 Y DW ? DATA ENDS CODE SEGMENT ;代码段定义 ASSUME CS:CODE,DS:DATA START: MOV AX,DATA MOV DS,AX FINISH: MOV AH,4CH INT 21H CODE ENDS END START 本例实现的是多分支结构。 设计多分支结构程序时,应注意: 要为每个分支安排出口; 各分支的公共部分尽量集中,以减少程序代码; 无条件转移没有范围的限制,但条件转移指令 只能在-128~+127字节范围内转移; 调试程序时,要对每个分支进行调试。 MOV AX,X CMP AX,0 JG ISPN JZ ISZN MOV Y,-1 JMP FINISH ISPN: MOV Y,1 ISZN: MOV Y,0
101
分支程序的设计 例4.4 有一组测试数据(有符号数),每个数据占用16位二进制数,数据个数存放在缓冲区的前2个字节,现要求分别统计出大于0、等于0和小于0的个数,分别存放在GREATZ、ZERO、LITTLEZ单元中。 解:有符号数的比较应该采用JG、JGE、JL、JLE等指令,同时还应该注意,MOV指令不会影响PSW中的标志位。 编程思路:将字单元GREATZ、ZERO、LITTLEZ用作为计数器,其初值均为0。然后对数据与“0”比较,当其大于0时,GREATZ单元加1;当其等于0时,ZERO单元加1;当其小于0时,LITTLEZ单元加1。程序如下
102
分支程序的设计 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD
STACK ENDS DATA SEGMENT BUFFER DW 500 ;假设有500个数据,并利 用重复宏随机产生 X=17 REPT 500 X=(X+979) mod 65535 DW X ENDM GREATZ DW ? ZERO DW ? LITTLEZ DW ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
103
分支程序的设计 START: MOV AX, DATA MOV DS, AX MOV ES, AX COUNT1:
MOV AX, STACK MOV SS, AX LEA SP,TOP XOR AX,AX MOV GREATZ,AX MOV ZERO,AX MOV LITTLEZ,AX MOV CX,BUFFER LEA SI,BUFFER+2 ST_COUNT: MOV AX,[SI] ADD SI,2 AND AX,AX JLE COUNT1 INC GREATZ JMP COUNT3 COUNT1: JL COUNT2 INC ZERO JMP COUNT3 COUNT2: INC LITTLEZ COUNT3: DEC CX JNZ ST_COUNT MOV AH,4CH INT 21H CODE ENDS END START OF =0 CF=0 AF=0
104
分支程序的设计 作业: 1 , 4, 7, 8 ,9
105
循环程序的设计 循环结构程序设计针对的是处理一些重复进行的过程的操作。采用循环结构设计的程序,其长度缩短了,不仅节省了内存,也使得程序的可读性大大提高。 通常将循环程序划分四个部分: 1.循环初始化部分。一般要进行地址指针、循环次数的设置,相关寄存器的清零等操作。只有正确地进行了初始化设置, 循环程序才能正确运行,及时停止。 2.循环体。是要求重复执行的程序段部分。 3.循环控制部分。由该部分修改并判断控制循环的条件是否满足,以决定是否继续循环。 4.循环结束部分。如保存循环运行结果等。
106
循环程序的设计 图 4 - 3循环程序结构 (a) 单循环结构; (b) 双循环结构
107
循环程序的设计 可直接采用的指针有4个: SI、DI、BX 、BP 默认段寄存器为 : ↓ ↓ DS SS
对一组数据的操作可以采用循环结构来实现,其数据个数为循环次数。每次循环时要不断地修改指针,而且经常会出现同时使用多个指针地情况。指针的正确使用是关系到程序设计好坏的重要因素 可直接采用的指针有4个: SI、DI、BX 、BP 默认段寄存器为 : ↓ ↓ DS SS ◆当程序中只需要一个指针时,可以使用SI、DI和BX中的任意一个; ◆ 当程序需要两个指针时,一般会要求程序从源操作数中取出要处理的数据,处理结果存放在另一个目的存储区域中,这样可以将SI指向源操作数区域,DI指向目的操作数区域; ◆当程序需要3个指针时,可以将BX用作为第三个指针;
108
循环程序的设计 ◆当需要4个以上的指针时,应该仔细分析数据区域的操作特点,找出可以采用同一个指针处理的多个区域。 指针的应用方式有两种:
(1)指针表示绝对地址:将指针指向存储单元的段内偏移地 址,例如: LEA SI, BUFFER MOV AX, [SI] 这样,指针SI的值表示缓冲区BUFFER的偏移地址,通过SI可以进行存储单元的访问。 (2)指针表示相对地址:将指针清零,表示指向存储区域内的相对偏移地址,例如: XOR SI, SI MOV AX, BUFFER[SI] 这样,指针SI就不是BUFFER所专用,而是可以同时供其它存储区域使用,例如可以通过: MOV BX, BUFFER[SI] 访问另一个存储区域SOURCE。
109
循环程序的设计 例4.4 将数据段中TABLE开始的连续100个 单元写入 0AAH,然后逐个读出进行 检查, 若发现有错,则置FLAG=1, 反之,置FLAG=0。试编写完整程序。 分析:通过循环,往指定存区写入0AAH, 然后逐个读出,与0AAH相比较, 全部相同时,0 Flag 如有不相同时,1 Flag 设置SI为地址指针,初值指向TABLE, 循环控制变量初值为100。
110
循环程序的设计 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD
STACK ENDS DATA SEGMENT TABLE DB DUP(100) FLAG DB ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP LEA SI, TABLE MOV CX, 100 L1: MOV [SI], 0AAH TEST [SI], 0AAH INC SI LOOPZ L1 JZ RIGHT MOV FALG, 1 JMP OVER RIGHT: MOV FLAG, 0 OVER: MOV AH,4CH ;返回DOS操作系统 INT 21H CODE ENDS END START
111
循环程序的设计 例4.5 设内存BUFF开始的单元中依次存放 着30个8位无符号数,求它们的和并 放在SUM单元中,试编写完整程序。
分析:这是一个求累加的程序。设置工作单 元存放累加和,初始值送0。然后逐个 读出数据,做累加和+数据 累加和, 循环进行N次。 设置SI为地址指针,初值指向BUFF, 循环控制变量初值为30。 111
112
循环程序的设计 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD
STACK ENDS DATA SEGMENT BUFF DB 30 DB ……… SUM DW ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP LEA SI, BUFF XOR CX, CX XOR AX, AX MOV CL , [SI] INC SI L1:ADD AL, [SI] ADC AH, 0 LOOP L1 MOV SUM AX OVER: MOV AH,4CH ;返回DOS操作系统 INT 21H CODE ENDS END START
113
循环程序的设计 例4.6在ARRAY开始的存区中存放着一组无符号字 数据,个数由COUNT指示。试编写程序 段寻找其中的最大数,放在MAX中。 分析:设置工作单元,先把第一个数放入, 然后逐个读出数据与其相比较, 如读出的数据大时,则数据 工作单元 如工作单元中的数据大时,则不送。 循环进行N-1次。 设置BX为地址指针,初值指向ARRAY, 循环控制变量初值为COUNT-1。 113
114
循环程序的设计 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD
STACK ENDS DATA SEGMENT ARRAY DW ……… COUNT DW N MAX DW ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP LEA BX, ARRAY MOV CX, COUNT MOV AX, [BX] DEC CX FINDMAX: JCXZ L1 INC BX CMP AX, [BX] JA L2 MOV AX,[BX] L2:LOOP FINDMAX L1: MOV MAX, AX OVER: MOV AH,4CH ;返回DOS操作系统 INT 21H CODE ENDS END START
115
循环程序的设计 例4.4 在SOURCE存储区中保存有500个字节数据,现在要求将数据中的0FFH值去掉,并传送到DESTINATION缓冲区中,其有效数据个数保存在NUMBER中。 解:将SI指向源操作数区域,DI指向目的操作数区域,每次传送一个字节,但在传送前对其内容进行检测,如果为0FFH,则不传送。在传送的同时要使有效数据个数的计数单元NUMBER进行加1。汇编语言程序如下: N=500 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT SOURCE LABEL BYTE ;假设有500个数据,并利用重复 X= ;宏随机产生 REPT 500 X=(X+97) mod 256 DB X ENDM DESTINATION DB N DUP(?) NUMBER DW ? DATA ENDS
116
循环程序的设计 MOVE1: CODE SEGMENT MOV AL,[SI] ASSUME CS:CODE,DS:DATA, INC SI
ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP XOR AX,AX MOV NUMBER,AX MOV CX,N LEA SI,SOURCE LEA DI,DESTINATION MOVE1: MOV AL,[SI] INC SI CMP AL,0FFH JZ MOVE2 MOV [DI],AL INC DI INC NUMBER MOVE2: LOOP MOVE1 MOVE_END: MOV AH,4CH INT 21H CODE ENDS END START
117
循环程序的设计 例4.5 在缓冲区DATABUF中保存有一组无符号数据(8位),其数据个数存放在DATABUF的第1、2个字节中,要求编写程序将数据按递增顺序排列。 解:这里采用双重循环实现数据的排序,这可使程序变得简单。要对N个数据进行从小到大排序时,可以采用“冒泡法”:从后往前,每两个数据进行比较,当前者大于后者时,交换两者的次序;否则不变,这样,经过N-1次比较,可以将最小值交换到第一个单元(最轻的气泡最先冒出水面)。接着对后N-1个数据,重复上述过程,使次小值交换到第二个单元;依此类推,共进行N-1次比较过程,可以完成数据的排序操作。
118
循环程序的设计 下面是对有7个元素的次序表进行冒泡排序的过程。 表的初始状态: [43 36 65 95 81 12 25]
表的初始状态: [ ] 第一遍扫描比较之后: 第二遍扫描比较之后: 第三遍扫描比较之后: 第四遍扫描比较之后: ………………………. 第五遍扫描比较之后: ……………………. 第六遍扫描比较之后:
119
循环程序的设计 由于每次比较操作都在相邻两个单元进行,因此只需要一个指针。汇编语言程序如下: N=100 ;设有100个数据
STACK SEGMENT STACK ‘STACK’ DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT DATABUF DW N DB N DUP(?) DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
120
循环程序的设计 生成随机数组 MOV BL,23 START: MOV AL,11 MOV AX,DATA LP: MOV [SI],AL
INC SI ADD AL,BL LOOP LP ; 下面给出数据排序程序 MOV CX,DATABUF DEC CX ;外循环次数 LEA SI,DATABUF+2 ;SI指向数据区首地址 ADD SI,CX ;SI指向数据区末地址 LP1: ;外循环开始 PUSH CX PUSH SI START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP ;为了能够进行排序,DATABUF ;中必须已经保存数据, ;因此我们产生一组随机数据 MOV CX,DATABUF LEA SI,DATABUF+2 生成随机数组
121
循环程序的设计 内循环的地址修正 外循环不用地址修,SI始终指向队尾 CX逐次减小 LP2: ;内循环开始,其循环次数恰好
MOV AL,[SI] CMP AL,[SI-1] JAE NOXCHG XCHG AL,[SI-1] ;交换操作 MOV [SI],AL NOXCHG: DEC SI LOOP LP2 POP SI POP CX LOOP LP1 ;数据排序结束 MOV AH,4CH ;返回DOS INT 21H CODE ENDS END START 内循环的地址修正 外循环不用地址修,SI始终指向队尾 CX逐次减小
122
循环程序的设计 例4.6 有一组数据(16位二进制数)存放在缓冲区BUF中, 数据个数保存在BUF的前两个字节中。要求编写程序实现在缓冲区中查找某一数据(16位),如果缓冲区中没有该数据,则将它插入到缓冲区的最后;如果缓冲区中有多个被查找的数据,则只保留第一个,将其余的删除。 解:在缓冲区BUF中搜索指定的数据,当没有找到该数据时,在最后插入该数据;当找到该数据时,则进入搜索多余的重复数据,每次找到该数据就删除它(即将缓冲区的剩余数据向前移动一个字)。当然还应该更新缓冲区的长度单元。 要删除数据时,可以采用例4.4的方法,开辟另一个存储区域,并且删除后还需要将数据传送回原来的存储区域。
123
循环程序的设计 我们还可以巧妙的利用指针,在同一个区域中实现删除功能。在例4.4的方法中,让目的操作数指针DI也指向源操作数区域,如图4.3所示,这样,每次的写操作可以写回到原来的存储区域,一开始SI与DI指针指向同一个地址,当找到需要删除的单元时,由于只有读操作,没有写操作,使DI指针落后于SI,从而完成将后续单元的内容向前移动的操作。 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT BUF DW 20 ; 设缓冲区原有20个字 DW 1000H,0025H,6730H,6758H,7344H,2023H,0025H,6745H,10A7H,0B612H DW 56AAH,15ACH,5789H,56AAH,6666H,7777H,56AAH,8888H,9999H,1111H DW 10 DUP(?) ;为可能的插入操作留出空间 NEW DW 56AAH ;指定的数据为(NEW)=56AAH DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX MOV ES,AX
124
循环程序的设计 没有找到,则插入数据 L3: MOV SS,AX MOV BX,[SI] ;读数据 LEA SP,TOP INC SI
MOV AX,STACK MOV SS,AX LEA SP,TOP ;搜索指定的数据 MOV CX,BUF LEA SI,BUF+2 MOV AX,NEW L1: CMP AX,[SI] JZ L2 INC SI LOOP L1 MOV [SI],AX INC BUF JMP OK ;结束 L2: ;找到第一个数据, ; 在剩余部分搜索并 ;进行删除操作 DEC CX MOV DI,SI ;DI与SI指向剩余 ;区域的首地址 L3: MOV BX,[SI] ;读数据 INC SI CMP AX,BX ;比较 JZ L4 MOV [DI],BX ;写数据 INC DI JMP L5 L4: DEC BUF ;更新长度计数器 L5: LOOP L3 OK: MOV AH,4CH ;返回DOS INT 21H CODE ENDS END START 没有找到,则插入数据
125
循环程序的设计 例4.7 在缓冲区DAT1和DAT2中,存放着两组递增有序的8位二进制无符号数,其中前两个字节保存数组的长度,要求编程实现将它们合并成一组递增有序的数组DAT,DAT的前两个字节用于保存新数组的长度。 DAT DAT2 DAT1 M+N M 00H 10H 25H 67H 68H N 00H 05H 12H 26H 45H 00H 05H DI 10H SI BX 12H 25H 26H 45H 67H … … 68H … 解:这里要用到3个指针,对于将数据写入数组DAT的指针首选使用DI,从DAT1和DAT2读数据的两个指针可分别采用SI和BX,并结合使用字符串指令,可以简化程序的设计。
126
循环程序的设计 解: 在程序设计中,将由BX指示 的缓冲区DAT2中的内容读入AL,这样, 当需要将DAT1的内容传送到DAT时,
可直接采用MOVSB指令;当需要将 DAT2的内容传送到DAT时,可直接 采用STOSB指令。流程框图如下:
127
循环程序的设计 例4.7 在缓冲区DAT1和DAT2中,存放着两组递增有序的8位二进制无符号数,其中前两个字节保存数组的长度,要求编程实现将它们合并成一组递增有序的数组DAT,DAT的前两个字节用于保存新数组的长度。 汇编语言程序如下: STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT DAT1 DW 10 ;设DAT1中有10个数据 DB 10H,25H,67H,68H,73H,83H,95H,0A8H,0C2H,0E6H DAT2 DW 13 ;设DAT2中有13个数据 DB 05,12H,26H,45H,58H,65H,67H,70H,76H,88H,92H,0CDH,0DEH DAT DW ? DB 200 DUP(?) DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK DAT1是字型变量,任何时候 都不能用 MOV AL, DAT1+N
128
循环程序的设计 想想看另一种算法是否能实现 L2: CMP AL,[SI] JB L3 MOVSB;DAT1区中的一个数据传送到DAT区
DEC CX JZ L4 JMP L2 L3: STOSB;DAT2区中的一个数据传送到DAT区 DEC DX JZ L5 JMP L1 L4: MOV SI,BX DEC SI MOV CX,DX L5: REP MOVSB;将DAT1或DAT2中剩余 ;部分全部传送到DAT区 MOV AH,4CH ;返回DOS INT 21H CODE ENDS END START START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP MOV CX,DAT1 ;CX表示DAT1的数据个数 MOV DX,DAT2 ;DX表示DAT2的数据个数 MOV DAT,CX ;先计算出DAT的数据个数 ADD DAT,DX LEA SI,DAT1+2 ;SI指向DAT1的数据区 LEA BX,DAT2+2 ;BX指向DAT2的数据区 LEA DI,DAT+2 ;DI指向DAT的数据区 CLD L1: MOV AL,[BX] INC BX 另一种思路:两个数组直接合并,然后用 冒泡法升序排列即可 想想看另一种算法是否能实现
129
循环程序的设计 例4.8 已知缓冲区BUFA内有20个互不相等的整数(其序号从0到19),缓冲区BUFB内有30个互不相等的整数(其序号从0到29)。编写程序完成:将既在BUFA中出现又在BUFB中出现的整数(设为x)存放在缓冲区BUFC中,并将x在BUFA和BUFB中的序号分别存放于缓冲区BUFCA和BUFCB中。 思路:一个大循环套一个小循环即可 解:这里涉及到5个存储区域,最好有5个指针,但BUFC、BUFCA和BUFCB为同步操作,即当找到x时,需要同时对BUFC、BUFCA和BUFCB进行操作,而且每个区域都写入一个字节,因此它们可以采用同一个指针,寻址方式为寄存器相对寻址,即设AL为找到的值,DL、BL为序号,则其操作为: MOV BUFC[DI],AL MOV BUFCA[DI],DL MOV BUFCB[DI],BL 采用寄存器相对寻址时, 例如 MOV AL,BUFA[SI], 其中SI即为该数据在该变量 中的序号。 汇编语言程序如下: BUFA BUFA BUFC BUFCA BUFCB BX SI DI … … … … …
130
循环程序的设计 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD
STACK ENDS N1=20 N2=30 DATA SEGMENT BUFA DB H,25H,67H,26H,68H,73H,83H,58H,0,06H,12H,0CDH,95H DB A8H,0C2H,48H,0E6H,0F1H,1AH,0F5H BUFB DB 05,12H,26H,45H,53H,60H,6AH,7FH,76H,88H,92H,0C1H,0DEH,0E1H,0F5H DB 09,17H,23H,48H,58H,65H,67H,70H,7CH,82H,96H,0CDH,0D1H,0F1H,0FEH BUFC DB 20 DUP(?) BUFCA DB 20 DUP(?) BUFCB DB 20 DUP(?) DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK
131
循环程序的设计 找到相同的值后,进行值传送和序号保存 START: MOV AX,DATA
MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP MOV CX,N1 XOR SI,SI XOR DI,DI L1: MOV AL,BUFA[SI] PUSH CX MOV CX,N2 XOR BX,BX L2: CMP AL,BUFB[BX] JZ L3 INC BX LOOP L2 JMP L4 以BUFA为外循环,每个字节与BUFB的所有字节比较(构成内循环), ;以确定是否存在相同的值 找到相同的值后,进行值传送和序号保存 L3: MOV BUFC[DI],AL MOV DX,SI MOV BUFCA[DI],DL MOV BUFCB[DI],BL INC DI L4: POP CX INC SI LOOP L1 MOV AH,4CH ;返回DOS MOV AL,0 INT 21H CODE ENDS END START 循环嵌套的保护
132
循环程序的设计 作业:10 ,11, 13,16
133
Thank You !
134
第四章 汇编语言 程序设计 任课教师:王晓甜
135
本章要点 1 2 3 4 5 6 汇编语言程序设计基础 源程序的汇编、链接与调试 分支程序的设计 循环程序的设计 子程序的设计
综合程序的设计 6
136
4.5 子程序设计 一:主程序与子程序之间的参数传递 (一)不进行任何参数传递 (二)寄存器参数传递方式 (三)存储单元参数传递方式
利用子程序可以大大地简化汇编语言的程序设计。宏指令是以存储空间作为代价提高执行速度的,而子程序是以降低执行速度来节省存储空间的。建议在多次调用较短的程序时使用宏指令,在多次调用较长的程序时使用子程序。 在子程序设计过程中,有几个问题需要特别注意: 一:主程序与子程序之间的参数传递 在设计子程序时,需要从主程序获取数据,这种数据称为入口参数,同时子程序执行后可能有结果数据要送给主程序,这种数据称为出口参数。主程序与子程序之间对入口参数和出口参数的传递有四种情况: (一)不进行任何参数传递 (二)寄存器参数传递方式 (三)存储单元参数传递方式 (四)堆栈参数传递方式
137
4.5 子程序设计 不进行任何参数传递 例1:一个延时子程序,其过程可定义如下: SOFTDLY PROC PUSH BX PUSH CX
MOV BL,10 DELAY:MOV CX,2801 WAIT: LOOP WAIT DEC BL JNZ DELAY POP CX POP BX RET SOFTDLY ENDP 不进行任何参数传递
138
4.5 子程序设计 用寄存器传递参数 入口参数为AX 出口参数为BL
COUNT PROC PUSH AX PUSH CX MOV CX,16 XOR BL,BL L1: SHL , AX 1 ADC BL, 0 LOOP L1 POP CX POP AX RET COUNT ENDP 用寄存器传递参数 入口参数为AX 出口参数为BL
139
4.5 子程序设计 注意: 作为出口参数的寄存器是不能保护的,否则就失去了传递参数的作用; 作为入口参数的寄存器可以保护也可以不保护。 由于寄存器的数量有限,这种方法只适用于少量数据的传递。 当有大量数据要传递时,需要用到指定单元或堆栈的方法传递参数。
140
4.5 子程序设计 用存储单元 传递参数 例3:统计 一组数据中55H的个数: STACK SEGMENT STACK 'STACK'
DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT BUF1 DB 10H, DB 23H,32H,55H,34H,98H,55H, DB 43H,55H,97H,64H BUF2 DB ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP CALL COUNT55 MOV AH,4CH ;返回DOS操作系统 INT 21H COUNT55 PROC NEAR PUSH AX PUSH CX PUSH BX XOR CX, CX MOV CL, BUF1 MOV AL, 55H MOV BUF2, 0 XOR BX,BX L1: INC BX CMP BUF1[BX],AL JNZ L2 INC BUF2 L2: LOOP L1 POP BX POP CX POP AX RET COUNT55 ENDP CODE ENDS END START 用存储单元 传递参数
141
4.5 子程序设计 用寄存器 传递存储器中的参数 例4:统计 一组数据中55H的个数: CALL COUNT55
MOV AH,4CH ;返回DOS操作系统 INT 21H COUNT55 PROC NEAR PUSH AX PUSH CX PUSH BX XOR CX, CX MOV CL, [SI] MOV AL, 55H MOV [DI], 0 XOR BX,BX L1: INC BX CMP [BX][SI],AL JNZ L2 INC [DI] L2: LOOP L1 POP BX POP CX POP AX RET COUNT55 ENDP CODE ENDS END START STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT BUF1 DB 10H, DB 23H,32H,55H,34H,98H,55H, DB 43H,55H,97H,64H BUF2 DB ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA, ES:DATA,SS:STACK START: MOV AX, DATA MOV DS, AX MOV ES, AX MOV AX, STACK MOV SS, AX LEA SP,TOP LEA SI, BUF1 LEA DI, BUF2 用寄存器 传递存储器中的参数
142
4.5 子程序设计 二、子程序说明文件 子程序为功能独立的程序段,而且会为主程序多次调用。因此为方便使用,在编写并调试好子程序后,应该及时给子程序编写相应的说明文件,其内容应该包含下列6个部分: 1、子程序名 2、子程序所完成的功能 3、入口参数及其传递方式 4、出口参数及其传递方式 5、子程序用到的寄存器 6、典型例子
143
4.5 子程序设计 三、子程序的嵌套 在子程序中还可以调用其他的子程序,这时就形成的子程序的嵌套如图4-5。
在设计嵌套子程序时,编程时可以从上到下设计,调试时应该由下至上进行,因为只有处于下层的子程序正确后,才能对上层的子程序进行调试。
144
4.5 子程序设计 四、递归子程序 在嵌套调用中,被调用的子程序为其他子程序。当被调用的子程序是其自身时,就形成了递归调用,这种子程序称为递归子程序。不是所有的子程序都可以递归调用的,设计递归子程序是一个较为复杂的过程,递归子程序必须具备两个基本条件: (1)采用堆栈参数传递方式,这样才能保证本次调用与下次调用采用不同的参数,即每次调用给入口和出口参数都分配不同的存储区域。 (2)必须设定递归结束条件。 设计递归子程序还应该有清晰的编程思路和明确的程序结构。设计递归子程序可以降低程序对存储容量需求,但现在计算机的存储容量已经不是问题了,因此,用户应该尽量避免采用递归子程序。
145
4.5 子程序设计 五、可再入性子程序 在执行子程序期间,CPU可能 会因为有中断请求而转向中断 服务子程序,如果在中断服务
程序中又调用了该子程序,这 样就形成了如图4.6 所示的情 况(①②③为执行流程),子 程序的一次调用还没有执行完 成,又调用了该子程序,如果 这两次调用都能够得到正确的 结果,则该子程序称为可再入 性子程序。 可再入性子程序也需要采用堆栈参数传递方式,而且设计过程较为复杂,因此建议用户尽量避免设计可再入性子程序。这里给出一种回避的较好方式,即将原本要求为可再入性的子程序复制出一份,专门供中断服务子程序调用,这样可以巧妙地回避可再入性子程序的设计。
146
4.5 子程序设计 六、应用举例 例4.9 编写子程序实现给缓冲区BUF中的一组字符的ASCII码加上偶校验位。 解:每个字符的ASCII码只占用7位二进制数,其最高位为0。我们可以根据这7位二进制数中“1”的个数,给最高位加上“0”或“1”,使得一个字节内容的“1”个数为偶数,这称为偶检验。 设计的子程序(SETEVEN)用于对BUFFER中的字符ASCII码加上偶检验位,其入口参数为:DI(缓冲区首地址,默认为DS段),(CX)缓冲区长度;出口参数:无(实际上是缓冲区的内容);用到的寄存器:无。 汇编语言程序如下: STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS N=22 DATA SEGMENT BUFFER DB 'xidian university 2006' DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP
147
4.5 子程序设计 主程序 子程序嵌套 被子程序调用的子程序,参数传递用的BL SETEVEN2: INC DI MOV CX,N
LOOP SETEVEN1 POP DI POP CX POP BX POP AX RET SETEVEN ENDP COUNTBYTE PROC NEAR;子程序:统计一个字 ;节内容中“1”的个数 PUSH AX PUSH CX MOV CX,8 XOR BL,BL 4.5 子程序设计 MOV CX,N LEA DI,BUFFER CALL SETEVEN MOV AH,4CH MOV AL,0 INT 21H SETEVEN PROC NEAR;加上偶校验子程序 PUSH AX PUSH BX PUSH CX PUSH DI SETEVEN1: MOV AL,[DI] CALL COUNTBYTE AND BL,01H;测试“1”的个数 ;是否为偶数 JZ SETEVEN2 OR AL,80H;最高位置入“1” MOV [DI],AL 主程序 COU1: SHR AL,1 ADC BL,0 LOOP COU1 POP CX POP AX RET COUNTBYTE ENDP CODE ENDS END START 被子程序调用的子程序,参数传递用的BL 子程序嵌套
148
4.5 子程序设计 例4.10 编写子程序TRANS16TO10,将16位二进制数(AX)转换成十进制数,并保存在指定的缓冲区中。
●将x除以10得到商x1和余数y1,其中y1就是转换结果的最 低位(个位); ●将x1再除以10得到商x2和余数y2,其中y2就是转换结果的十位; ●依次类推,得到y3、 y4和y5,分别为转换结果第三~五位。 可以肯定,x5=0,y5位为转换结果的最高位。设计的子程序TRANS16TO10,其入口参数:AX(待转换的数据),DI(转换结果存储区域首地址),出口参数:存储区域的内容。
149
4.5 子程序设计 汇编语言子程序如下: TRANS16TO10 PROC NEAR POP DI PUSH AX POP DX
PUSH BX PUSH CX PUSH DX PUSH DI MOV BX,10 MOV CX,5 TRANS1: XOR DX,DX DIV BX MOV [DI],DL INC DI LOOP TRANS1 POP DI POP DX POP CX POP BX POP AX RET TRANS16TO10 ENDP
150
4.5 子程序设计 例4.11 编写子程序DISPAXD,将16位二进制数(AX)转换成十进制数,并在显示在屏幕上。
然后利用INT 21H的02号功能进行显示, 这时需要将十进制的数位变换成相应的 ASCII码。设计出的子程序DISPAXD,其 入口参数:AX,出口参数:屏幕显示, 这里要用到一个5字节的临时存储单元, 用于存放十进制的数位。 STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT DECIMAL DB 5 DUP(?) DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP
151
4.5 子程序设计 MOV AX,23456 CALL DISPAXD MOV AH,4CH ; MOV AL,0 ;返回码 INT 21H
DISPAXD PROC NEAR PUSH AX PUSH BX PUSH CX PUSH DX PUSH DI LEA DI,DECIMAL CALL TRANS16TO10 MOV CX,5 LEA DI,DECIMAL+4 MOV AH,2 DISPAXD2: MOV DL,[DI] ADD DL,30H DEC DI INT 21H LOOP DISPAXD2 POP DI POP DX POP CX POP BX POP AX RET DISPAXD ENDP TRANS16TO10 PROC NEAR ;内容参见例4.10 TRANS16TO10 ENDP CODE ENDS END START 子程序嵌套
152
4.5 子程序设计 例4.12 编写子程序实现:将输入缓冲区中以ASCII码表示的十进制数转换成16位二进制数。缓冲区的第一个字节表示位数,后续单元存储十进制数,高位在前,低位在后。如果转换结果超出一个字的范围,则在BX中置出错标志(FFFFH)。 解:这种存放格式与通过键盘输入十进制数的格式一致。变换算法与例4.9相反,设十进制数字符变换成数值后为x1~x5,x1为最低位,则变换结果y为: y=10*(10*(10*(10*x5+x4)+x3)+x2)+x1 据此可以编写出子程序TRANS10TO16,入口参数:SI(缓冲区首地址);出口参数:AX(变换结果)和BX(变换结果是否出错标志);用到的寄存器:AX和BX。子程序如下:
153
4.5 子程序设计 TRANS10TO16 PROC NEAR PUSH CX PUSH DX ADD AL,DL PUSH SI
XOR AX,AX XOR CX,CX MOV CL,[SI] INC SI MOV AL,[SI] SUB AL,30H DEC CX JCXZ TRANSF2 MOV BX,10 TRANSF1: MUL BX JC TRANSF_ERR MOV DL,[SI] SUB DL,30H ADD AL,DL ADC AH,0 JC TRANSF_ERR LOOP TRANSF1 MOV BX,0 TRANSF2: JMP TRANSF_OK TRANSF_ERR: MOV BX,-1 TRANSF_OK: POP SI POP DX POP CX RET TRANS10TO16 ENDP
154
4.5 子程序设计 例4.13 利用键盘输入十进制的无符号数,编写程序完成转换成相应的16位二进制数。
解:可以直接利用例4.11编写的子程序完成转换操作。通过键盘输入数据时,需要定义键盘缓冲区,并调用INT 21H的0AH号功能。汇编语言程序如下: STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT KEYBUFFER DB 100 ;键盘缓冲区 DB ? DB 100 DUP(?) STRING1 DB 'Please input decimal data : ','$' ;输入提示字符串 DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP
155
4.5 子程序设计 LEA DX,STRING1 ;显示出“输入提示字符串” MOV AH,09H INT 21H
MOV AH,0AH ;输入十进制数据 LEA DX,KEYBUFFER LEA SI,KEYBUFFER+1 ;变换 CALL TRANS10TO16 CALL DISPCR ;屏幕光标回车换行 CALL DISPAX ;显示变换结果 CALL DISPCR MOV AX,BX CALL DISPAX ;显示变换结果是否正确的标志BX)的内容 MOV AH,4CH ;返回DOS MOV AL,0
156
4.5 子程序设计 DISPCR PROC NEAR ;屏幕光标回车换行子程序 TRANS10TO16 PROC NEAR PUSH AX
PUSH DX MOV AH,2 MOV DL,0AH INT 21H MOV DL,0DH POP DX POP AX RET DISPCR ENDP TRANS10TO16 PROC NEAR ;详见例4.12 TRANS10TO16 ENDP CODE ENDS END START DISPAX PROC NEAR ;显示寄存器AX的内容子程序 ;详见例3.34 DISPAX ENDP
157
4.5 子程序设计 例4.14 设一组16位有符号数存放在缓冲区BUFFER中,前两个字节用于存放数据个数,编写子程序COMPUTMEAN计算这组数据平均值。 解:计算数据平均值的子程序COMPUTMEAN,采用堆栈参数传递方式,人口参数:缓冲区首地址压入堆栈;出口参数:计算出的平均值存入堆栈,采用与保存缓冲区首地址相同的堆栈单元。汇编语言程序如下: STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT BUFFER DW 10 ;假设有10个数据 DW 521,112,3654,-564,45, -166,771,1288,32709,-32014 DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP
158
4.5 子程序设计 MOV AX, OFFSET BUFFER PUSH CX PUSH AX PUSH DX ;入口参数压入堆栈进行传递
MOV SI,[BP+4] ;从堆栈中取出入口参数,即数据区的首地址 XOR DX,DX XOR BX,BX XOR DI,DI MOV CX,[SI] ;取数据区长度 PUSH CX ;暂存数据个数 ADD SI,2 CPTM1: MOV AX,[SI] CWD ADD BX,AX PUSH AX ;入口参数压入堆栈进行传递 CALL COMPUTMEAN POP AX ;出口参数也通过堆栈得到 CALL DISPAX ;调用子程序DISPAX显示AX的内容 MOV AH,4CH ;返回DOS系统 INT 21H COMPUTMEAN PROC NEAR ;计算平均值子程序 PUSH BP MOV BP,SP ;利用指针BP指向堆栈中的固定位置 PUSH SI PUSH DI PUSH BX
159
4.5 子程序设计 ADC DI,DX LOOP CPTM1 MOV DX,DI MOV AX,BX POP BX ;取出数据个数
IDIV BX ;求数据的平均值 CPTM2: MOV [BP+4],AX ;在堆栈中保存数据的平均值 POP DX POP CX POP BX POP AX POP DI POP SI POP BP
160
4.5 子程序设计 RET COMPUTMEAN ENDP CODE ENDS END START 利用堆栈参数传递方式时,一定
要搞清楚堆栈的结构和指针的位置, 在进入子程序后,其堆栈结构与指 针如图4.6所示,随着子程序中PUSH 和POP指令的操作,堆栈指针SP在移 动,但BP指针的位置固定不变,因此 ,可以利用BP指针取出入口参数,同 时将处理结果存放到指定的堆栈区域 。这样的子程序为可再入性子程序。
161
4.5 子程序设计 例4.15 递归子程序设计。设计子程序完成y=n! 的计算。
解:假设已经设计了计算k阶阶乘的子程序factorial,为说明方便采用f(k)表示,则k+1阶阶乘可以表示成: f(k+1)=(k+1)×f(k) 这样,计算n阶阶乘的过程可以用图4.7所示。因此,当计算n!时,要调用n次factorial子程序,每次调用使k值减1,直到k=1,这时将1!=1作为已知结果,然后一层一层返回。
162
4.5 子程序设计
163
4.5 子程序设计 为设计递归子程序factorial,需要定义一个字单元RESULT用于存放计算结果,因此,factorial的入口参数为:正整数k(≤8)和RESULT单元的偏移地址,出口参数为RESULT单元的内容。汇编语言子程序factorial如下: N=7 ;计算7!,其结果应该为5040 (13B0H) STACK SEGMENT STACK 'STACK' DW 100H DUP(?) TOP LABEL WORD STACK ENDS DATA SEGMENT RESULT DW ? DATA ENDS
164
4.5 子程序设计 CODE SEGMENT ASSUME CS:CODE,DS:DATA,ES:DATA,SS:STACK START:
MOV AX,DATA MOV DS,AX MOV ES,AX MOV AX,STACK MOV SS,AX LEA SP,TOP LEA SI,RESULT PUSH SI ;结果单元的偏移地址压入堆栈 MOV AX,N PUSH AX ;N值压入堆栈 CALL FACTORIAL
165
4.5 子程序设计 MOV AX,RESULT ;取出结果,并显示 CALL DISPAX MOV AH,4CH ;返回DOS操作系统
INT 21H FACTORIAL PROC NEAR ;计算N!的递归子程序 PUSH BP MOV BP,SP PUSH BX PUSH AX MOV BX,[BP+6] ;从堆栈中取出存放结果的地址 MOV AX,[BP+4] ;从堆栈中取出k值 CMP AX,1 ;结束条件判断 JE FACT1 DEC AX ;k=k-1
166
4.5 子程序设计 CALL FACTORIAL ;递归调用FACTORIAL MOV BX,[BP+6] ;从堆栈中取出存放结果的地址
PUSH AX CALL FACTORIAL ;递归调用FACTORIAL MOV BX,[BP+6] ;从堆栈中取出存放结果的地址 MOV AX,[BX] ;取出结果 MUL WORD PTR [BP+4] ;计算k!=k*(k-1)! JMP FACT2 FACT1: MOV AX,1 FACT2: MOV [BX],AX ;保存结果 POP AX POP BX POP BP RET ;递归子程序返回,并修正SP指针 FACTORIAL ENDP
167
4.5 子程序设计 CODE ENDS END START ★设计递归子程序的关键在于搞清楚堆栈结构与指针的使用。子程序FACTORIAL执行时,其前三次调用的堆栈结构如图4.8所示。第一次由主程序调用,其返回地址也是主程序中CALL指令的下一条语句的地址,BP指针指向堆栈的适当位置,这时可以通过BP取出结果单元的地址和N值;第二次调用为递归调用,是由子程序FACTORIAL本身所引起的,其返回地址为子程序中CALL指令的下一条语句的地址,BP指针又指向了堆栈新的位置,这时可以通过BP取出结果单元的地址和N-1值;第三次调用与第二次调用类似,只是通过BP可以取出结果单元的地址和N-2值;依此类推,直至N=1。这个过程可以描述成从上到下的调用过程。从RET处返回时,与调用过程次序相反,是从下到上的一个返回过程,分析过程类似。
168
4.5 子程序设计
169
4.5 子程序设计 作业:19, 21, 25
170
Thank You !
Similar presentations