✅
文章目录
编程时刻!⌨️
上一篇文章链接: 【计算机与UNIX汇编原理⑦】——DOS/BIOS功能调用【实现两数相加 + Hello World!】.
下一篇文章链接: 【计算机与UNIX汇编原理⑨】——实验报告一【题目:排除语法错误、统计数字】.
一、汇编程序概述
● 汇编语言编程属结构化程序设计。
● 汇编程序一般采用自上而下的设计方法,步骤为:
① 分析题意,确定算法
② 根据算法画出程序框图
③ 根据框图编写程序
④ 上机调试程序
⑤ 执行程序
● 从结构上来讲,一般的汇编程序可分为:
① 顺序程序
② 分支程序
③ 循环程序
④ 子程序
⑥ 宏指令
● 转移指令可用来实现分支程序设计,分支程序分三种:
① 简单分支
② 复合分支
③ 多分支
二、顺序程序设计
● 和 C 语言的顺序结构一样。
三、分支程序设计
● 这里我们举两个例子来熟悉。
● 样例一【简单分支】:二进制数显示将 BX 寄存器中的内容 (5678H) 以二进制数格式 → 屏显
● 程序框图:
● 代码如下:
.586
CODE SEGMENT USE16
ASSUME CS:CODE
BEG: MOV BX, 5678H ; 装入要显示的内容
MOV CX, 16 ; 循环次数:因为要显示 16 次二进制码
LAST:MOV AL, '0' ; 0 的 ASCII 码
ROL BX, 1 ; (不含进位的循环)左移
JNC NEXT ; 若当前 C 标志为 0 时, 则进行转移
MOV AL, '1' ; 1 的 ASCII 码
NEXT:MOV AH, 0EH ; 功能号0EH(BIOS):显示
INT 10H
LOOP LAST ; CX 将执行减 1 操作, 小于 0 时跳出循环
MOV AH, 4CH ; 功能号4CH(DOS)
INT 21H
CODE ENDS
END BEG
◆ 代码说明:因为该程序不涉及内存中的数据,所以不用定义数据段,只定义了代码段。
● 运行结果:
◆ 结果说明:“0101011001111000” → “0101 0110 0111 1000” → “5 6 7 8”
● 样例二【复合分支】:某科室9人,统计月收入在 800~900 间的人数,并用十进制数显示。
● 代码如下:
.586
DATA SEGMENT USE16
NUM DW 666, 930, 810, 850, 800, 900, 1000, 2000, 600
DATA ENDS
CODE SEGMENT USE16
ASSUME CS:CODE, DS:DATA
BEG: MOV AX, DATA
MOV DS, AX
MOV BX, OFFSET NUM
MOV CL, 9 ; 循环 9 次
MOV DL, 0 ; 记录 800~900 之间的个数
LAST: CMP WORD PTR [BX], 800
JC NEXT ; < 800 时转
CMP WORD PTR [BX], 900
JA NEXT ; > 900 时转
INC DL
NEXT: INC BX ; 每个数占两个字节的单元, 所以需要自增两次
INC BX
DEC CL
JNZ LAST
ADD DL, 30H ; 将 DL 中存储的数字转化为 ASCII 码
MOV AH, 2 ; 功能号02H(显示)
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END BEG
● 运行结果:
◆ 结果说明:
① 810、850、800、900 这 4 个数字符合条件,所以答案是 4 。
② 月收入是无符号数,对一批数处理应该用间址。
四、循环程序设计
1、循环程序的组成
● 循环初始化部分:进入循环操作之前的准备工作。比如循环计数器的设置、缓冲区偏移地址的设置。通常使用寄存器或者内存单元作为循环计数器。
● 循环体:重复执行的程序代码。它是循环程序的核心部分。它会完成具体的、重复执行的工作以及为进入下次循环而进行的调整工作,如对循环次数的修改,缓冲区偏移地址的修改。
● 循环控制部分:判断循环条件是否成立。如果未满足,则继续循环,否则退出循环。
2、循环程序的结构
● 循环程序的结构一般有两种:单循环结构、双循环结构。
3、样例
● 【找出最大数】:设从 BUF 单元开始,存有若干字节的 ASCII 码,找出其中的最大数 → 屏幕显示。【注:ASCII 码为无符号数,无符号数的最小数为 0 】
● 程序框图:
● 代码如下:
.586
DATA SEGMENT USE16
BUF DB 'ABCD123EFG'
COUNT EQU $-BUF ; 统计串的长度
MAX DB 'MAX=', ?, 0DH, 0AH, '$' ; 格式化输出串
DATA ENDS
CODE SEGMENT USE16
ASSUME CS:CODE, DS:DATA
BEG: MOV AX, DATA
MOV DS, AX
MOV AL, 0 ; 先将无符号最小数 0 → AL
LEA BX, BUF ; 串首址的偏移量 → BX
MOV CX, COUNT ; 串长度 → CX
LAST: CMP [BX], AL
JC NEXT
MOV AL, [BX] ; 将最大数 → AL
NEXT: INC BX
LOOP LAST ; CX-1 → CX, 结果不为 0 时, 进行转移
MOV MAX+4, AL ; 再将最大数 → 变量 "MAX的 ? 中去"
MOV AH, 9 ; 显示
MOV DX, OFFSET MAX
INT 21H
MOV AH, 4CH
INT 21H
CODE ENDS
END BEG
● 运行结果:
五、子程序设计
● 子程序是相对独立的程序。当程序中要多次完成某一操作时,为了简化整体程序,增强程序可读性。常常把 “完成某一操作” 设计成一个子程序,供主程序调用。【注:在汇编语言中,子程序也称为过程】
● 子程序设计规则:
① 子程序用 PROC/ENDP 定界。
② 子程序分为:段内子程序、段间子程序、无参数子程序、有参数子程序。
③ 向子程序传递参数的方法:通过寄存器传递参数、利用堆栈区传递参数、利用内存单元传递参数。
"无参数子程序的模板:"
CODE SEGMENT
...
CALL CRLF
...
CALL CRLF
...
"举例:无参数子程序"
CRLF PROC
MOV AH, 2
MOV DL, 0DH ; 回车(CR)
INT 21H
MOV AH, 2
MOV DL, 0AH ; 换行(LF)
INT 21H
RET
CRLF ENDP
◆ 说明:
回车符:将光标带到行的开头,(carriage return)
换行符:将光标向下移动一个空格(line feed)
"例:用寄存器传递参数"
MESG1 DB 'Please Enter 0~9:$' ; 数据段
MESG2 DB 'Strike any key', 0DH, 0AH,'$'
CODE SEGMENT ; 代码段
...
MOV DX,OFFSET MESG1
CALL DISP
...
MOV DX,OFFSET MESG2
CALL DISP
...
;----名为 DISP 的子程序--------
DISP PROC
MOV AH, 9
INT 21H
RET
DISP ENDP
CODE ENDS
六、宏指令与条件汇编
● 子程序调用可以简化程序,但是如果子程序的参数太多,调用时太麻烦,汇编语言提供了另一种简化的途径——宏指令。
● “宏” 在计算机领域里的一般概念:将一系列操作组织成为一个整体的功能。【例:Windows Shell中的宏操作】
● 宏指令的补充说明:
① 宏指令是汇编语言提供的伪指令,它是用户自行定义的若干指令的集合。
② 宏指令有:无参数宏指令、有参数宏指令。
③ 宏指令应该先定义后调用。
④ 宏指令的定义,可以不在任何逻辑段之中,习惯上放在源程序首部。
1、无参数宏指令的定义与调用
■ 定义格式:
宏指令名字 MACRO
...宏体...
ENDM
■ 宏指令调用格式:宏指令先行定义之后,在代码段的任何位置,书写 “宏指令名字” 即完成了调用。【本质上就是一种 “替换” 】
● 宏指令的汇编过程:汇编时,汇编程序自动用宏体取代宏调用(即宏指令名字所在处)。
"举例"
.586
CRLF MACRO ; 实现回车换行的 “宏”
MOV AH,0EH
MOV AL,0DH
INT 10H
MOV AL,0AH
INT 10H
ENDM
;-------------------
CODE SEGMENT USE16
ASSUME CS:CODE
BEG: MOV AH,2
MOV DL,'A'
INT 21H
CRLF ; 汇编时, 自动用宏体替换宏指令, 将其拷贝一份到这里
2、有参数的宏指令及其调用
■ 定义格式:
宏指令名字 MACRO 用逗号(或空格)间隔的哑元表
宏体
ENDM
■ 调用格式:宏指令名字 实元表
◆ 注意:实元表是一串用逗号(或空格)间隔的立即数,寄存器操作数或者是没有 PTR 说明符的存储器操作数。汇编时,汇编程序自动地把实元一一对应的赋给哑元
3、LOCAL伪指令
● 若宏体中有分支、转移,则必然有标号。若调用两次以上这种宏体,会出现标号重复定义的错误,为此汇编语言提供了 LOCAL 伪指令。
■ 格式:LOCAL 用逗号间隔的标号名
● 应用:
① 用 LOCAL 定义的标号称为 “局部标号” ,它是宏体中所有标号的集合。局部标号可以和代码段中其他标号重名。
② 宏指令仅被调用 1 次时,不必使用 LOCAL 伪指令。
"例: 分组显示 8、16、32 位二进制数, 并将变量 NUM 的数据以二进制的形式输出:"
.586
CRLF MACRO ; 光标返回下一行始格
MOV AH, 0EH
MOV AL, 0DH
INT 10H
MOV AL, 0AH
INT 10H
ENDM
;------------------
DISP MACRO VAR, NN ; 分组显示 VAR 中的 NN 位二进制数
LOCAL L1, L2 ; LOCAL 伪指令
MOV CH, NN/4 ; 得到分组数 → CH
L1: MOV CL, 4
L2: MOV AL, '0'
ROL VAR, 1 ; 显示 1 位二进制数
ADC AL, 0
MOV AH, 0EH
INT 10H
DEC CL
JNZ L2
MOV AL, ' ' ; 显示 “间隔”
INT 10H
DEC CH
JNZ L1
CRLF
ENDM
;------------------
CODE SEGMENT USE16
NUM DB 88H
ASSUME CS:CODE
BEG: MOV EBX, 12345678H
DISP BH, 8 ; 显示 BH 中的 8 位数
DISP BX, 16 ; 显示 BX 中的 16 位数
DISP EBX, 32 ; 显示 EBX 中的 32 位数
DISP NUM, 8 ; 显示 NUM 单元中的 8 位数
MOV AH, 4CH
INT 21H
CODE ENDS
END BEG
● 运行结果:
4、宏指令和子程序调用的相同点和不同点
● 相同点:都可以简化程序设计。在调用发生1次以上时,都可以减小源程序的尺寸。
● 不同点:
① 调用发生的时刻不同:宏调用在程序汇编时刻发生,由汇编程序完成调用功能;子程序调用在程序运行时刻发生,由CPU执行调用指令并完成调用功能。宏调用对目标程序的尺寸减小无任何贡献;而子程序调用对目标程序的尺寸减小有贡献(当调用发生1次以上时)。
② 调用需要的时间不同:宏调用是替换操作,需要的时间相对短;子程序调用是程序控制流转移即由主程序 ——> 子程序 ——> 主程序的过程,并且使用堆栈保存断点地址,需要的时间相对长。
③ 调用的方式和参数传递方式不同:宏调用使用宏的名称即可,子程序调用需要 CALL 以及 RET 指令;宏调用通过形参和实参一一对应实现参数传递,子程序调用通过寄存器,堆栈以及内存单元传递参数。
七、参考附录:
[1] 《微型计算机原理与接口技术(慕课板)》
清华大学出版社
[2] 《汇编语言程序设计(第2版)》
上一篇文章链接: 【计算机与UNIX汇编原理⑦】——DOS/BIOS功能调用【实现两数相加 + Hello World!】.
下一篇文章链接: 【计算机与UNIX汇编原理⑨】——实验报告一【题目:排除语法错误、统计数字】.
⭐️ ⭐️