目录
作者:李宗霖 日期:2023/5/22
一、实验目的
1、学习子程序(过程)的定义和调用方法。
2、学习$、OFFSET等伪指令。
3、进一步学习程序执行的流程。
二、实验内容
设有一数据区有10个有符号数的字节数据(如55,78H,43,-19,0,1,10,80H,0FFH,100),位于数据段偏移地址为1000H开始的10个内存单元中。编写程序找出其中的最大数和最小数,分别存入BH和BL;然后求其中最大值和最小值的平均值,存入AL。
要求:将求最大最小值设计为子程序,并画出子程序流程图;在主程序里调用该子程序、求平均值,并绘制主程序流程图。
流程图
代码
理解:
方便大家理解这里用面对对象的类来进行类比,只是类比,汇编语言没有类的概念!
段内过程就像在一个类里面,只用改变偏移地址即可调用。段外过程就像不在一个类中的函数,要调用就得申明,也就是这里存储要调用代码段的段地址和偏移地址。
使用段内过程:
DATA SEGMENT ; 定义数据段
ARRAY DB 55, 78H, 43, -19, 0, 1, 10, 80H, 0FFH, 100
; MAX: 78H = 120D, MIN: 80H = -128D
NUM DW 10D ; 数据的数量
OFFS DW 1000H ; 存放数据的偏移地址
DATA ENDS
CODE SEGMENT ; 定义代码段
ASSUME CS: CODE, DS: DATA ; 将各段内容指定寄存器
START: MOV AX, DATA
MOV DS, AX
MOV ES, AX ; 将数据段DATA的首地址放入DS, ES
MOV AX, 0H
MOV CX, NUM ; 传数据数量
MOV SI, OFFSET ARRAY ; 传数据地址给SI
MOV DI, OFFS ; 目标偏移地址传给DI
REP MOVSB ; 将数据段的10条字节数据存入指定偏移地址
CALL FIND_MAX_MIN ; 调用子程序,寻找最大值和最小值
MOV AL, BH ; 将最大值放入AL
ADD AL, BL ; 将最小值加到AL
SAR AL, 1 ; 将AL右移一位,相当于除以2,得到平均值
MOV AH, 4CH ; 结束主程序运行
INT 21H
FIND_MAX_MIN PROC NEAR ; 定义段内子程序,寻找最大值和最小值
PUSH AX ; 保护寄存器AX
PUSH CX ; 保护寄存器CX
PUSH SI ; 保护寄存器SI
MOV CX, NUM ; CX传入数据数量
MOV SI, OFFS ; 用SI指向数据存放地址
MOV BH, -128 ; 初始化最大值为-128(有符号数的最小值)
MOV BL, 127 ; 初始化最小值为127(有符号数的最大值)
FIND_LOOP: MOV AL, [SI] ; 取出当前元素放入AL
CMP AL, BH ; 比较当前元素和最大值
JLE CMP_MIN ; 如果当前元素小于等于最大值,与最小值比较
MOV BH, AL ; 如果当前元素大于最大值,更新最大值
CMP_MIN: CMP AL, BL ; 比较当前元素和最小值
JGE NEXT_ELEMENT ; 如果当前元素大于等于最小值,下一个数
MOV BL, AL ; 如果当前元素小于最小值,更新最小值
NEXT_ELEMENT: INC SI ; 增加数组地址指针
LOOP FIND_LOOP ; 循环计数器减一,如果CX不为零,继续循环
POP SI ; 恢复寄存器SI
POP CX ; 恢复寄存器CX
POP AX ; 恢复寄存器AX
RET ; 返回主程序
FIND_MAX_MIN ENDP ; 结束子程序定义
CODE ENDS ; 代码段结束
END START
使用段外过程:
DATA SEGMENT ; 定义数据段
ARRAY DB 55, 78H, 43, -19, 0, 1, 10, 80H, 0FFH, 100
; MAX: 78H = 120D, MIN: 80H = -128D
NUM DW 10D ; 数据的数量
OFFS DW 1000H ; 存放数据的偏移地址
DATA ENDS
CODE SEGMENT ; 定义代码段
ASSUME CS: CODE, DS: DATA ; 将各段内容指定寄存器
START: MOV AX, DATA
MOV DS, AX
MOV ES, AX ; 将数据段DATA的首地址放入DS, ES
MOV AX, 0H
MOV CX, NUM ; 传数据数量
MOV SI, OFFSET ARRAY ; 传数据地址给SI
MOV DI, OFFS ; 目标偏移地址传给DI
REP MOVSB ; 将数据段的10条字节数据存入指定偏移地址
MOV WORD PTR [DI+2], OFFSET FIND_MAX_MIN
; 将子程序的偏移地址存入DI+2处
MOV WORD PTR [DI+4], CODE_FIND
; 将子程序的段地址存入DI+4处
CALL FAR PTR [DI+2] ; 调用子程序,寻找最大值和最小值
MOV AL, BH ; 将最大值放入AL
ADD AL, BL ; 将最小值加到AL
SAR AL, 1 ; 将AL右移一位,相当于除以2,得到平均值
MOV AH, 4CH ; 结束主程序运行
INT 21H
CODE ENDS
CODE_FIND SEGMENT ; 定义子程序代码段
ASSUME CS:CODE_FIND ; 代码段指定寄存器
FIND_MAX_MIN PROC FAR ; 定义段内子程序,寻找最大值和最小值
PUSH AX ; 保护寄存器AX
PUSH CX ; 保护寄存器CX
PUSH SI ; 保护寄存器SI
MOV CX, NUM ; CX传入数据数量
MOV SI, OFFS ; 用SI指向数据存放地址
MOV BH, -128 ; 初始化最大值为-128(有符号数的最小值)
MOV BL, 127 ; 初始化最小值为127(有符号数的最大值)
FIND_LOOP: MOV AL, [SI] ; 取出当前元素放入AL
CMP AL, BH ; 比较当前元素和最大值
JLE CMP_MIN ; 如果当前元素小于等于最大值,与最小值比较
MOV BH, AL ; 如果当前元素大于最大值,更新最大值
CMP_MIN: CMP AL, BL ; 比较当前元素和最小值
JGE NEXT_ELEMENT ; 如果当前元素大于等于最小值,下一个数
MOV BL, AL ; 如果当前元素小于最小值,更新最小值
NEXT_ELEMENT: INC SI ; 增加数组地址指针
LOOP FIND_LOOP ; 循环计数器减一,如果CX不为零,继续循环
POP SI ; 恢复寄存器SI
POP CX ; 恢复寄存器CX
POP AX ; 恢复寄存器AX
RETF ; 返回主程序
FIND_MAX_MIN ENDP ; 结束子程序定义
CODE_FIND ENDS
END START
对于进位和溢出的优化问题:
;不考虑溢出进位求平均数
MOV AL, BH ; 将最大值放入AL
ADD AL, BL
SAR AL, 1 ; 将AL右移一位,相当于除以2,得到平均值
MOV AH, 4CH ; 结束主程序运行
;优化考虑进位、溢出改为如下:
JNO NOT_OVERFLOW ; 如果不溢出跳转NOT_OVERFLOW处理
ADC AH, 0H ; 当最大最小值都是80H进位
SHR AX, 1 ; 溢出将AL逻辑右移一位,得到平均值
JMP FINISH ; 跳转结束
NOT_OVERFLOW: SAR AL, 1 ; 在不溢出时,用算术右移一位
FINISH: MOV AH, 4CH ; 结束主程序运行
对无符号数进位进行处理:
;无符号数考虑进位求平均数
MOV AL, BH ; 将最大值放入AL
ADD AL, BL
ADC AH, 0H
SHR AX, 1 ; 将AL右移一位,相当于除以2,得到平均值
MOV AH, 4CH ; 结束主程序运行
程序编译信息截图
程序运行结果截图
三、实验调试过程记录、结果分析及评价
-
特别注意有符号数字节取值范围:-128~127
-
注意段内过程还段外过程,缺省默认是段内NEAR,段外FAR
-
段外需要注意返回主程序是RETF,调用子程序是要先存储子程序偏移地址和段地址,不然访问不到:
-
求平均数注意有符号运算用算数右移:SAR
-
(2023/5/23优化) 如果上题是无符号数,只需将进位存入AH中,做除法时目标为AX即可。
-
(2023/5/23优化)在求平均数的时候注意对溢出的处理:如果溢出采用逻辑右移,不溢出采用算数右移。最特殊 的情况是最大最小值都为80H,进位到AH,用逻辑右移处理。