当前位置:课程学习>>第三章>>知识讲解>>视频课堂>>知识点四
本节将介绍汇编语言的程序结构,顺序结构、分支结构、循环结构,以及子程序。
汇编语言程序有3种基本结构:
顺序结构
分支结构
循环结构
1. 顺序结构
采用这种结构的程序,按照指令书写的顺序逐条执行,程序的执行路径没有分支和循环。
编程将内存数据段字节单元INDAT存放的一个数n(假设0≤ n ≤9 ),以十进制形式在屏幕上显示出来。例如,若INSTR单元存放的是数8,则在屏幕上显示:8D。
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
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
上述代码的解读和仿真执行效果,在以下视频[录屏\03 8086汇编语言程序设计_4.mp4]中。
代码可以在示例代码4.asm中下载。
2.分支结构
分支结构程序利用条件转移指令或跳转表,使程序执行完某条指令后,根据指令执行后状态标志的情况选择要执行哪个程序段。分支结构程序的指令执行顺序与指令的存储顺序不一致。无条件跳转转移指令JMP和条件跳转Jcc可以实现分支结构。
例:
单分支
编写程序段,求AX中存放的带符号数的绝对值,结果存在RES单元。
CMP AX,0
JGE ISPOSITIVE ;如果AX中的值大于0,那么跳转到ISPOSITIVE,把AX
NEG AX 否则,对AX取负号
ISPOSITIVE: ;如果AX大于0,那么,未执行取负号操作
MOV RES,AX ;无论是否取负号,都把AX移入RES中
…
例:
双分支
编程判断DAT单元存放的带符号数的正负。如该数为负数,则显示“DAT is a negative number!”;否则显示“DAT is a nonnegative number!”。
DATA SEGMENT ;数据段定义
N DB 'DAT is a negative number!','$'
NN DB 'DAT is a nonnegative number! $'
DATA ENDS
CODE SEGMENT ;代码段定义
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX ;设置DS
MOV AX,-3
CMP AX,0
JGE ISNN
LEA DX,N
MOV AH,9
INT 21H
JMP FINISH
ISNN: LEA DX,NN
MOV AH,9
INT 21H
FINISH:
MOV AH,4CH
INT 21H
CODE ENDS
END START
上述代码的解读和仿真执行效果,在视频[录屏\03 8086汇编语言程序设计_5.mp4]中。
代码可以在示例代码5.asm中下载。
例
多分支
编程求分段函数Y的值。已知变量X为16位带符号数,分段函数的值要求保存到字单元Y中。函数定义如下。
流程图如下:
DATA SEGMENT ;数据段定义
X DW -128
Y DW ?
DATA ENDS
CODE SEGMENT ;代码段定义
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV AX,X
CMP AX,0
JG ISPN
JZ ISZN
MOV Y,-1
JMP FINISH
ISPN:
MOV Y,1
JMP FINISH
ISZN:
MOV Y,0
FINISH:
MOV AH,4CH
INT 21H
CODE ENDS
END START
上述代码可以在 示例代码 6.asm 中,可以下载。
代码的讲解和运行仿真,请看视频[录屏 6]。
分析上述程序中需要注意:
(1)要为每个分支安排出口;
(2)各分支的公共部分尽量集中,以减少程序代码;
(3)无条件转移没有范围的限制,但条件转移指令只能在-128~+127字节范围内转移;
(4)调试程序时,要对每个分支进行调试。
3.循环结构
当程序处理的问题需要包含多次重复执行某些相同的操作时,在程序中可使用循环结构来实现,即用同一组指令,每次替换不同的数据,反复执行这一组指令。使用循环结构,可以缩短程序代码,提高编程效率。
循环程序由以下3个部分组成。
(1)初始化部分
初始化部分是循环的准备部分,在这部分应完成地址指针、循环计数、结束条件等初值的设置。
(2)循环体
循环体包括以下3个部分。
循环工作部分:是循环程序的主体。完成程序的基本操作,循环多少次,这部分语句就执行多少次。
循环修改部分:修改循环工作部分的变量地址等,保证每次循环参加执行的数据能发生有规律的变化。
循环控制部分:控制循环执行的次数,检测和修改循环控制计数器,控制循环的运行和结束。
(3)循环结束部分
在循环结束部分,完成循环结束后的处理,如数据分析、结果的存放等。
设计循环结构程序时,要注意以下问题:
① 选用计数循环还是条件循环,采用直到型循环结构还是当型循环结构;
② 可以用循环次数、计数器、标志位、变量值等多种方式来作为循环的控制条件,进行选择时,要综合考虑循环执行的条件和循环退出的条件;
③ 注意不要把初始化部分放到循环体中,循环体中要有能改变循环条件的语句,否则特环无法退出。
例:
当型循环,变量值‘!’作为跳出条件
编程显示以“!”结尾的字符串,如:“Welcome to MASM!”
DATA SEGMENT
MYSTR DB 'Welcome to MASM!'
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX
LEA SI,MYSTR
NEXTCHAR:
MOV DL,[SI]
CMP DL,'!'
JZ FINISH
MOV AH,2
INT 21H
INC SI
JMP NEXTCHAR
FINISH:
MOV AH,2
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS
END START
上述代码可以在 示例代码 7.asm 中,可以下载。
代码的讲解和运行仿真,请看视频[录屏 7]。
由于只知道循环结束的条件是该字符串以“!”结束,不知道字符串的长度,所以,采用了条件控制的方法来控制循环的跳出条件。
例:
计数循环,计数器CX
编程以二进制形式显示BX的值(假设为无符号数)。如果(BX)=20,那么显示:0000000000010100B。
CODE SEGMENT
ASSUME CS:CODE
START:
MOV BX,0ffh ;20
MOV CX,16 ; 计数器CX,置初值16
NEXTCHAR:
ROL BX,1 ;显示顺序是从左往右,
MOV DL,BL ;要显示的值仅占最低位D0
AND DL,1 ;清除D7~D1
OR DL,30H
MOV AH,2
INT 21H ;利用2号DOS调用显示
LOOP NEXTCHAR ;循环执行16次
FINISH:
MOV DL,'B'
MOV AH,2
INT 21H ;利用2号DOS调用,显示'B'
MOV AH,4CH
INT 21H ; 返回操作系统
CODE ENDS
END START
上述代码可以在 示例代码 8.asm 中,可以下载。
代码的讲解和运行仿真,请看视频[录屏 8]。
本例中,由于已知BX是16位的,因此,循环的次数就是16次,所以可以采用计数法控制循环。
4.子程序设计
汇编代码除8086微处理器的机器指令外,还可以使用DOS操作系统和BIOS提供的功能。这些常用功能可以方便程序设计,更快更稳定地完成开发。
8086汇编语言程序采用模块化结构,通常由一个主程序模块和多个子程序(过程)模块构成。对于简单程序,只有主程序模块,没有子程序模块。
在许多应用程序中,常常需要多次用到一段程序。这时,为了避免重复编写程序,节省内存空间,可以把该程序段独立出来,以供其他程序调用,这段程序称为“子程序”或“过程”。调用子程序的程序体,称为“主程序”或“调用程序”。
子程序是可供其他程序调用的具体特定功能的程序段。子程序被多次调用,但是只编写一次。如果不写成子程序,那么在软件工程中接下来的频繁修改中,每次都需要修改多段代码,工作量大且容易造成不一致的现象。
子程序编写时需要注意以下4点。
(1)现场保护和恢复
所谓“现场保护”是指子程序运行时,对可能破坏的主程序用到的寄存器、堆栈、标志位、内存数据值进行的保护,暂时保存在其他的位置,避免进入子程序以后被修改,从子程序返回后以后主程序因此混乱。
所谓“现场恢复”指由子程序结束运行返回主程序时,对被保护的寄存器、堆栈、标志位、内存数据值的恢复。常利用堆栈和空闲的存储区实现现场保护和现场恢复。
在C语言的函数调用中,自动把调用函数(相当于主程序或调用程序)的局部变量压栈保护,在函数退出后弹栈恢复。这些工作是由编译器所形成的汇编指令自动完成的,C语言程序员通常不需要考虑。局部变量在编译时会要求运行时置于栈中。但是汇编语言程序员要更多考虑低层的情况,所有的存储器中的变量和寄存器的生命周期和作用域都类似C语言的全局函数,保护和恢复工作需要由程序员完成。
(2)子程序嵌套
一个程序可以调用某个子程序,该子程序可以调用其他子程序,这就形成了子程序嵌套。子程序嵌套调用的层次不受限制,其嵌套层数称为“嵌套深度”。由于子程序中使用堆栈来保护断点,堆栈操作的“后进先出”特性能自动保证各个层次子程序断点的正确入栈和返回。在嵌套子程序设计中,应注意寄存器的保护和恢复,避免各层子程序之间寄存器发生冲突。特别是在子程序中使用PUSH、POP指令时,要格外小心,以免造成子程序无法正确返回。
在C语言的函数调用中,子程序嵌套调用甚至递归调用,也自动使用了堆栈,不需要C语言程序员考虑PUSH、POP指令,但是仍然需要考虑堆栈溢出的情况。只是因为初学者较小的程序较少遇到这样的情形,所以C语言程序员通常并不关心这一细节。
(3)参数传递
主程序在调用子程序时,经常需要向子程序传递一些参数或控制信息,子程序执行完成后,也常常需要把运行的结果返回给调用程序,这种调用程序和子程序之间的信息传递,称为“参数传递”。参数传递的主要方法有:寄存器传递、内存变量传递、堆栈传递。
传递的内容如果是数据本身,称为“值传递”;如果是数据所在单元的地址,称为“地址传递”。
在C语言中,函数的参数传递都是值传递。在C++中,这时还需要考虑构造函数、复制函数、operator=()操作符。在C/C++中,函数参数是指针的情况,传递了地址。
(4).编写子程序调用方法说明
无论是为了建立库供他人使用,还是为了方便自己以后阅读和使用子程序,都应编写子程序调用说明。
子程序调用方法说明包括 子程序功能、入口参数、出口参数、使用(将会修改,需要调用过程先行保护,调用结束后恢复)的寄存器或存储器,以及调用实例。这与DOS功能调用与BIOS功能调用的说明规范类似。
例
利用寄存器传递参数。
编写子程序,实现以二进制形式显示BX的值(假设为无符号数)。
; ---------------------
; 主程序
; ---------------------
CODE SEGMENT
ASSUME CS:CODE
START:
mov bx, 0ffffh ; 20
call DISP_BINARY
MOV AH,4CH
INT 21H
;-------------------------
;子程序名:DISP_BINARY
;功能:以二进制形式显示BX的值(假设为无符号数)
;入口参数:BX
;出口参数:无
;------------------------
DISP_BINARY PROC
PUSH CX
PUSH DX
PUSH AX
PUSHF ;保护现场
MOV CX,16
NEXTCHAR:
ROL BX,1
MOV DL,BL
AND DL,1
OR DL,30H
MOV AH,2
INT 21H
LOOP NEXTCHAR
FINISH:
MOV DL,'B'
MOV AH,2
INT 21H
POPF ;恢复现场
POP AX
POP DX
POP CX
RET
DISP_BINARY ENDP
CODE ENDS
END START
本例利用寄存器BX由主程序向子程序传递参数。作为出口参数的寄存器是不能保护的,否则就失去了传递参数的作用;作为入口参数的寄存器可以保护也可以不保护。由于寄存器的数量有限,这种方法只适用于少量数据的传递。当有大量数据要传递时,需要用到指定单元或堆栈的方法传递参数。
代码可以在示例代码9.asm中下载。
代码的讲解和运行效果在视频[录屏 9]中。
例:
利用指定存储单元进行参数传递,编程利用子程序实现数据块的复制。
;SSEG SEGMENT
; DW 64 DUP(?)
; TOS LABEL WORD
;SSEG ENDS
DATA SEGMENT
BUF1 DB 1,2,3,4,5,6,7,8,9,100
BUF2 DB 10 DUP(0)
SRCADDR DW ?
DSTADDR DW ?
LEN DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,ES:DATA ;,SS:SSEG
START:
MOV AX,DATA
MOV DS,AX
MOV ES,AX;
MOV AX,SSEG;
MOV SS,AX;
MOV SP,OFFSET TOS
LEA AX,BUF1
MOV SRCADDR,AX ; 置源数据区首地址
LEA AX,BUF2
MOV DSTADDR,AX ; 置目的数据区首地址
MOV LEN,10 ; 置数据块长度
CALL MOVEMYDAT ;调用子程序MOVEMYDAT
MOV AH,4CH
INT 21H
;------------------------------------
;子程序名:MOVEMYDAT
;功能:数据块复制
;入口参数: 源数据区首地址存于SRCADDR
; 目的数据区首地址存于DSTADDR
; 数据块长度存LEN
;出口参数:无
;------------------------------------
MOVEMYDAT PROC
MOV SI,SRCADDR
MOV DI,DSTADDR
MOV CX,LEN
STD
ADD SI,CX
DEC SI
ADD DI,CX
DEC DI
BEGIN:
REP MOVSB
RET
MOVEMYDAT ENDP
CODE ENDS
END START
代码可以在示例代码a.asm中下载。
代码的讲解和运行效果在视频[录屏 a]中。
本例利用指定存储单元进行参数传递。这种方法实现的子程序通用性较差。
例:
利用堆栈进行参数传递。
编程利用子程序求2个含有10个元素的无符号字节数组AD1和AD2对应元素之和,计算结果存入SUM字节数组(不考虑运算结果溢出的情况)。
SSEG SEGMENT STACK 'STACK'
DW 64 DUP(?)
TOS LABEL WORD
SSEG ENDS
DATA SEGMENT
AD1 DB 1,2,3,4,5,6,7,8,9,100
AD2 DB 2,3,4,5,6,7,8,9,10,100
SUM DW 10 DUP(?)
LEN EQU 10
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:SSEG,ES:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV ES,AX
MOV AX,SSEG
MOV SS,AX
MOV SP,OFFSET TOS
MOV CX,LEN
LEA SI,AD1
LEA DI,AD2
LEA BX,SUM
NEXT:
PUSH [SI]
PUSH [DI]
CALL ADD_B
MOV [BX],AX
INC SI
INC DI
INC BX
LOOP NEXT
MOV AH,4CH
INT 21H
;------------------------------------
;子程序名:ADD_B
;功能:求字节和
;入口参数:堆栈
;出口参数:无
;------------------------------------
ADD_B PROC
MOV BP,SP
MOV AX,[BP+2]
ADD AX,[BP+4]
RET 4
ADD_B ENDP
CODE ENDS
END START
此代码可在 代码示例 b.asm 下载。
代码的讲解和运行效果在视频[录屏 b]中。