子程序与模块化

1. 课程说明

本课程基于《汇编语言(第 2 版)》郑晓薇 编著,机械工业出版社,可以配合该教材使用。本课程由郑晓薇授权制作,提取教材中的实例以及实验内容,可以在实验楼环境中完成所有实例及实验。实验课程制作符合教材原版实例驱动教学以及实验训练贯穿始终的特点。

本教材可在 当当网 购买(点击链接即可进入购买页面)。

本课程实验列表中的最后模块为配套教材的习题参考答案,可供读者学习参考。

2. 实验环境

DOS 环境:

实验环境中安装有 dosemu 可以模拟 DOS 环境,并提供 DEBUGMASMLINK 等汇编语言开发程序。

3. 子程序(过程)

在复杂的程序设计中,采用模块化结构可以划分功能,分解程序;使程序由复杂变为简单,由混乱变为清晰,程序代码易读。

在各个子程序之间存在参数传递问题,因此在编写程序时,确定传参方式和嵌套调用层次十分重要。

另外,每个子程序的功能应该用注释标明,程序采用的算法和指令的用途也应该标注出来,便于以后的阅读和修改。

接下来,让我们先学习几个子程序设计的例子。

3.1 子程序例一

本例子为教材的示例 7-1。

**关注点**:子程序结构 ,子程序的调用与返回

前几章介绍的 5-7.ASM 的程序只能从键盘输入一个两位的十进制数,用十六进制显示。如果想输入多位的十进制数还要修改程序,增加更多的条件判断转移指令,将进一步加大程序的复杂性,而采用子程序结构可以较好地解决问题。

设计目标

多次输入一个 65535 以内的十进制数并以十六进制显示出来。按 ESC 键结束。

设计思路

(1)设主程序标号 LET0,一个子程序标号为 LET1,另一个子程序标号 LET2;

(2)主程序是一个死循环,只有当按下 ESC 键时才能退出、结束程序;

(3)子程序 LET1 功能为键盘输入,并把输入的数字变为十进制数,保存在 X 单元;

(4)子程序 LET2 功能为通过查表将 X 单元中的数值用十六进制显示出来。

程序框图

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

;7-1.asm  用子程序多次输入一个65535以内的十进制数并以十六进制显示出来。按ESC键结束
data segment
x dw 0
mess1 db 0dh,0ah,'input dec=$'
mess2 db 0dh,0ah,'out HEX=$'
hex db '0123456789ABCDEF'
data ends
code segment
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
;主程序
let0: mov x,0
mov dx,offset mess1            ;显示提示1
mov ah,9
int 21h
call let1                        ;调用子程序1
call let2                        ;调用子程序2
jmp let0
;子程序1:键盘输入、形成十进制数x
let1: mov ah,1                        ;键盘输入十进制数
int 21h
cmp al,27                        ;是ESC键?
jz out1
sub al,30h                        ;其它字符?
jl exit                        ;是,转exit
cmp al,9
jg exit    
mov ah,0        
xchg ax,x                        ;形成十进制数
mov cx,10
mul cx    
add x,ax                        ;相加并保存
jmp let1
exit:     ret                            ;ret返回主程序
;子程序2:查表,显示十六进制
let2: mov dx,offset mess2                ;显示提示2
mov ah,9
int 21h
mov bx,x                        ;将x→bx
mov ch,4
mov cl,4        ;将bx中的二进制数循环左移4位(十六进制数循环左移1位)
rept1:rol bx,cl                    ;例如0021→0210→2100→1002→0021
mov ax,bx                        
and ax,000fh                    ;保留最低4位
mov si,ax
mov dl,hex[si]                    ;查表显示十六进制数
mov ah,2
int 21h
dec ch                            ;显示下一位
jnz rept1
ret                            ;返回主程序
out1: mov ah,4ch
int 21h
code ends
end start

(2)在 dos 子目录下保存为 7-1.asm,经过汇编 masm 7-1.asm,连接 link 7-1.obj,生成 7-1.exe。

D:\dos〉masm 7-1.asm
D:\dos〉link 7-1.obj
D:\dos〉7-1.exe

(3)运行结果:

分析程序得知, 子程序调用 CALL 指令应该和子程序返回 RET 指令成对出现,使用了 CALL 指令,子程序中一定要有 RET 返回指令。本例中程序结构清晰,各个子程序功能相对独立,程序运行灵活。

**思考**:为什么 RET 指令能够返回到主程序?CALL 指令在执行时实际上做了什么?

3.2 子程序例二

本例子为教材的示例 7-2。

**关注点**:过程定义伪指令

在示例 7-1 中,子程序是以第一条指令的标号作为子程序名调用的。所谓调用,实际上就是程序转移到该标号去继续执行。这种方式虽然简便,但是在模块化程序结构设计中,是不规范的。尤其是其它模块中的某个程序想要调用这个子程序时,还需要指明该子程序标号是在哪个模块、哪个代码段的哪个程序中。标准的用法是用 8086 汇编语言提供的过程定义伪指令 PROC 来定义子程序。

设计目标

从键盘键入一个多位十进制数 X,回车结束输入。按十进制位相加后显示十进制结果 Y。

设计思路

(1)主程序分别调用三个子程序。

(2)子程序 SUBR1 为键盘输入多位十进制数且直接保存到 X,输入的位数在 BX;

(3)子程序 SUBR2 将保存的 X 去掉 ASCII 码,按位相加,相加的结果在 BX 中;

(4)子程序 SUBR3 将 BX 中的数用十进制显示;

(5)采用将结果除以 10 保存余数的方法将 BX 中的数转换为十进制数,并用十进制数的 ASCII 码显示结果。

(6)传参寄存器为 BX。

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

;7-2.asm  键入一个十进制数x,按位相加后显示十进制结果y。
data segment
  infor1 db 0ah,0dh,'x=$'
  infor2 db 0ah,0dh,'y=$'
        x  db 20 dup(?)
data ends
code segment 
assume cs:code,ds:data
start:    mov ax,data
mov ds,ax
;主程序
main proc far        ;主程序定义,远程的
mov x,0
mov dx,offset infor1            ;显示提示1
mov ah,9
int 21h
mov bx,0                        ;传参寄存器bx清0
call subr1                    ;调子程序1
mov cx,bx                    ;保存x的位数
mov ax,0
mov bx,0
call subr2                    ;调子程序2
mov dx,offset infor2            ;显示提示2
mov ah,9
int 21h    
call subr3                    ;调子程序3
jmp main
out1:    mov ah,4ch
int 21h
main endp
;子程序1:键盘输入、保存
subr1 proc near        ;定义子程序1,近程的
mov ah,1                        ;键盘输入十进制数
int 21h
cmp al,0dh                    ;回车?
jz exit
cmp al,'0'                    ;其它非法字符?
jl out1                        ;是转out1,直接退出
cmp al,'9'
jg out1    
mov x[bx],al                    ;保存键入的数码
inc bx                        ;BX=数码个数
jmp subr1
exit:    cmp bx,0                    ;第一键就是回车
jz out1
ret                            ;返回主程序
subr1 endp
;子程序2,按位相加
subr2 proc near        ;定义子程序2,近程的
mov ah,x[bx]                    ;取出键入的数码
and ah,0fh                    ;去掉ASCII码
add al,ah                    ;按位相加
inc bx
loop subr2                    ;循环累加
mov ah,0
mov bx,ax                    ;相加结果→bx传参寄存器
ret                            ;返回主程序
subr2 endp
;子程序3,将bx中的数显示为十进制数
subr3 proc near        ;定义子程序3,近程的
mov ax,bx                    ;bx为传参寄存器
mov cx,0
mov bx,10                    
let1:                                ;将ax变为十进制数
mov dx,0                        ;字除法的高字清0
inc cx                        ;统计余数个数
div bx                        ;除以10,商在AX,余数在DX
push dx                        ;保存余数
cmp ax,0
jnz let1
let2:                                ;循环显示余数,循环次数在cx中
pop ax                        ;将余数弹入ax
add ax,0030h                    ;调整为ASCII码
mov dl,al                    ;2号功能显示
mov ah,2
int 21h
loop let2
ret                            ;返回主程序
subr3 endp
code ends
end start

(2)在 dos 子目录下保存为 7-2.asm,经过汇编 masm 7-2.asm,连接 link 7-2.obj,生成 7-2.exe。

D:\dos〉masm 7-2.asm
D:\dos〉link 7-2.obj
D:\dos〉7-2.exe

(3)运行结果:

3.3 子程序例三

**关注点**:存储单元地址的传参

设计目标

求两个数组 ARRAY1 和 ARRAY2 的正数累加和。累加和分别放在 TOTAL1 和 TOTAL2 中。

设计思路

(1)采用 BX 和 SI 寄存器分别存放数组的地址和累加和单元的地址,将地址值传送到子程序中;

(2)在子程序的运算过程中用基址变址方式到存储器中取出数组元素;

(3)子程序的运算结果通过寄存器间接寻址方式保存到存储单元。

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

;7-4.asm  分别求两个数组ARRAY1和ARRAY2的正数累加和。累加和分别放在TOTAL1和TOTAL2中。
data segment 
array1 dw 3,2,-5,8,-7
array2 dw 4,-1,5,6,2
total1 dw ?
total2 dw ?
m dw 5
data ends
code segment
assume cs:code,ds:data
main proc far
start:    mov ax,data
    mov ds,ax
    mov cx,m                        ;数组元素个数
    lea bx,array1                    ;数组1的偏移地址→bx传参寄存器
    lea di,total1                    ;累加和1的偏移地址→di传参寄存器
    call summ                        ;调子程序,求数组1的正数累加和
    lea bx,array2
    lea di,total2
    call summ                        ;求数组2的正数累加和
    mov ah,4ch
    int 21h
main endp
;子程序summ求累加和
summ proc near
    push cx                            ;保存循环次数
    mov ax,0
    mov si,0
loop1:cmp word ptr[bx][si],0            ;判断正负数
    jle exit
    add ax,[bx][si]                    ;求正数的累加和
exit: add si,2
    loop loop1
    mov word ptr[di],ax                ;保存到存储单元,用存储单元传参
    pop cx
    ret
summ endp
code ends
    end start

运行说明:
在 DEBUG 下调试执行。执行带有子程序的程序时,要注意在找断点时要找程序结束指令 MOV AH,4CH 和 INT 21H,而不要随意进入子程序。因为主程序是用 CALL 指令进入子程序的,涉及到堆栈操作,子程序返回时是通过 RET 返回的,要修改栈指针。稍有不慎,就会使堆栈混乱,造成死机。

(2)在 dos 子目录下保存为 7-4.asm,经过汇编 masm 7-4.asm,连接 link 7-4.obj,生成 7-4.exe。

D:\dos〉masm 7-4.asm
D:\dos〉link 7-4.obj
D:\dos〉7-4.exe
D:\dos〉debug 7-4.exe

(3)运行结果:

断点是 001F,执行 G 001F 之后,子程序也一起执行了。查看数据段,可看到在 0014 单元是第一个累加和等于 000DH,其后为第二个累加和为 0011H。

3.4 子程序例四

本例子为教材的示例 7-5。

设计目标

利用子程序编程实现矩阵的乘法 C = A × B。

矩阵的乘法:A=a(m,n) 和 B=b(n,m),其结果矩阵为 C=c(m,m)。其元素 c(i,j) 为 A 的第 i 行向量与 B 的第 j 列向量的内积。(设 m=3,n=4)

设计思路

(1)实现矩阵乘法需要三重循环。

(2)采用主程序 MAIN 负责最外层循环,控制 A 矩阵的行;子程序 SUBR1 用双重循环完成 A 矩阵的一行与 B 矩阵所有列的乘加。

(3)设置 Y 单元为 A 矩阵每行的首地址,Y 以 4 为间隔增加。将 Y 入栈传参,子程序从堆栈中读取 Y → bx。进入子程序后,由于栈指针 SP 要改变,因此用 BP 作为取参的指针;Y 在当前栈指针 +4 的堆栈单元中。

(4)用 BX、SI、DI 寄存器作为三个矩阵 A、B、C 的下标;BX 以 1 为间距增加,SI 以 3 为间距增加,DI 以 1 为间距增加。

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

; 7-5.asm  堆栈传参。实现两个矩阵的乘法c=a*b 
data segment
  a db 1,1,1,1
    db 2,2,2,2
    db 3,3,3,3
  b db 1,1,1
    db 2,2,2
    db 3,3,3
    db 4,4,4
 m  dw 3                        ;A矩阵3行4列
 n  dw 4                        ;B矩阵4行3列
 y  dw 0
 c  db 9 dup(?)                    ;C矩阵3行3列
data ends
code segment
   assume cs:code,ds:data
main proc far
start:    mov ax,data
    mov ds,ax
       mov cx,m                    ;主程序循环次数3次
    mov di,0
    mov si,0
rept3:push y                    ;用堆栈传参,保存y
    push cx                        ;保存主程序中循环次数
    mov cx,m                    ;子程序外循环次数
    call subr1
    pop cx
    pop y                        ;弹出保存的A上一行首址
    add y,4                        ;指向A矩阵下一行
    loop rept3    
    mov ah,4ch
    int 21h
main endp
;子程序subr1
subr1 proc near
    mov bp,sp                    ;call指令后的栈指针值→bp
rept2:    push cx                    ;共做3列
    mov bx,[bp+4]                ;从堆栈中取出y→bx
    mov si,m                    ;m=3
    sub si,cx                    ;B的起始下标
    mov cx,n                    ;n=4,子程序内循环次数            
rept1:                
    mov al,a[bx]                ;A的下标变化0,1,2,3
    mov dl,b[si]
    imul dl                        ;乘加
       add c[di],al                ;C矩阵
    inc bx                        ;A行的下一个元素
    add si,3                    ;B的一列
    loop rept1                    ;循环4次
    inc di                        
    pop cx
    loop  rept2            
    ret
subr1 endp
code ends
     end start

(2)在 dos 子目录下保存为 7-5.asm,经过汇编 masm 7-5.asm,连接 link 7-5.obj,生成 7-5.exe。

D:\dos〉masm 7-5.asm
D:\dos〉link 7-5.obj
D:\dos〉7-5.exe
D:\dos〉debug 7-5.exe

(3)运行结果:

运行说明:
(1)矩阵 C 的 9 个元素从 001E 单元开始存放,分别是十六进制的 0A、0A、0A,14、14、14,1E、1E、1E。
(2)程序中堆栈变化情况如下:(图中字母改为 Y 了)

可看出,主程序中的两条指令 PUSH 指令和一条 CALL 指令把相关的参数入栈保存;进入子程序后先执行了”mov bp,sp”,把此时的栈指针 SP 保存到基址指针 BP 中;接着又执行”push cx”,栈指针指在当前 SP 位置上。BP+4 就是 Y 所在栈单元的地址。

3.5 子程序例五

本例子为教材的示例 7-6。

在复杂的程序设计中,采用模块化结构可以划分功能,分解程序;使程序由复杂变为简单,由混乱变为清晰,程序代码易读。在各个子程序之间存在参数传递问题,因此在编写程序时,确定传参方式和嵌套调用层次十分重要。另外,每个子程序的功能应该用注释标明,程序采用的算法和指令的用途也应该标注出来,便于以后的阅读和修改。

设计目标

从键盘输入学生姓名和成绩,按成绩升序排序,并显示出排序结果。

本题目的关键之处在于从键盘输入的姓名和成绩都是 ASCII 码,排序时成绩要变为二进制数或 BCD 码。排序时不光是成绩顺序要改变,而且姓名顺序也要随之改变,显示的结果才正确。这是一个较大的程序,采用模块化结构将功能分解,用子程序调用和嵌套实现。

设计思路

(1)主程序和 5 个子程序。子程序分别是 INPUT 键盘输入、 COPY 数据转存、CHANGE 十进制数 ASCII 码 → 二进制、SORT 按成绩排序和 PRINT 打印排序名单。

(2)用变量 P 控制输入的人数,本例中 P = 3。

(3)姓名和成绩输入分别用 DOS 中断调用的 10 号功能实现字串输入。由于 10 号功能可以设定输入的字符个数和获得实际输入个数,使用方便。但输入最后字符之后,回车符 0DH 也被保存;需要将其改为$,便于输出时直接用 9 号功能显示姓名和成绩。

(4)用 BUFFER1 和 BUFFER2 作为键入的姓名和成绩的缓存区,然后将所有人名和成绩用串传送指令转存到 SNAME 和 SCORE1 中保存,打印输出时可以利用。

(5)将 SCORE1 中成绩的十进制数 ASCII 码转换为二进制数→SCORE2;

(6)按 SCORE2 中的成绩排序,同时将保存在 MINGCI 中的输入次序号也一起交换,以次序号作为排序指针,在 SNAME 和 SCORE1 中查找相应的人名和成绩;

(7)打印排序名单时,从 MINGCI 中取出次序号作为位移量,到 SNAME 和 SCORE1 中取出姓名和对应的成绩用 9 号功能显示。排序后 MINGCI 中先取出的次序号一定是成绩最高的人的,其他类推。

程序框图

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

;7-6.asm  从键盘输入学生姓名和成绩,按成绩升序排序。
data segment
    infor0 db 0ah,0dh,'sort=$'
    infor1 db 0ah,0dh,'input name:$'
    infor2 db 0ah,0dh,'input score:$'
    n equ 8                            ;姓名长度
    m equ 4                            ;成绩长度(3位+回车符)
    p equ 3                            ;输入的人数
    q equ 3                            ;成绩位数
    buff1 db n,?,n+1 dup('$')        ;姓名缓冲区,加$符以便输出时用
    buff2 db m,?,m+1 dup('$')        ;成绩缓冲区
    sname db p dup (n+1 dup('$'))    ;保存姓名
    score1 dw p dup (m+1 dup('$'))    ;保存成绩
    score2 dw p dup (m+1 dup(0))
    mingci db p dup(0)                ;名次
    x dw ?
    sign1 dw 0
    sign2 dw 0
    cont db '1'                        ;计数
data ends
code segment
    assume cs:code,ds:data,es:data
main proc far
start:
    mov ax,data
    mov ds,ax
    mov es,ax
    mov bx,0
    mov cx,0
    call input
    call sort
    call print
    mov ah,4ch
    int 21h
main endp
;子程序1,输入姓名、成绩
input proc
    inc bx                            ;输入次数统计
    cmp bx,p                        ;输入次数>p?
    ja exit
    lea dx,infor1                    ;显示提示1 
    mov ah,9
    int 21h
    lea dx,buff1                    ;输入姓名
    mov ah,10
    int 21h
    mov al,buff1+1                    ;实际输入个数→al
    add al,2                        ;+2,包含buff1的0,1号单元
    mov ah,0
    mov si,ax                        ;回车0d所在位置,跟在最后一个字符后
    mov buff1[si],'$'                ;将0d换为$,便于输出显示
    lea dx,infor2                    ;显示提示2
    mov ah,9
    int 21h
    lea dx,buff2                    ;输入成绩
    mov ah,10
    int 21h
    mov al,buff2+1                    ;实际输入个数
    add al,2                        ;个数+2,包含0,1单元,为找到0d
    mov ah,0
    mov si,ax
    mov buff2[si],'$'                ;将0d换为$,便于输出显示
    mov mingci[bx-1],bl                ;bx为输入次数,保存输入的次序
    cmp bx,1                        ;第一次输入转let1
    jz let1
    add sign1,n+1                    ;姓名间隔为n+1
    add sign2,q                        ;成绩间隔为q
let1:
    call copy                        ;子程序嵌套
    jmp input
exit:
    ret
input endp
;子程序2,数据转存
copy proc
    mov cx,n+1                        ;姓名长度+1(包含$)
    lea si,buff1+2
    lea di,sname                    ;姓名传送到sname
    add di,sign1                    ;加上间隔值
    cld
    rep movsb
    mov cx,n
    mov ax,'$'                        ;用$覆盖姓名区,清除已输入的姓名
    lea di,buff1+2
    rep stosb
    mov cx,m+1                        ;成绩位数+1(包含$)
    lea si,buff2+2
    lea di,score1                    ;成绩传送到score1
    add di,sign2                    ;加上间隔值
    cld
    rep movsb
    lea si,buff2+2
    mov di,sign2                    
    call change                        ;二进制成绩→score2
    ret
copy endp
;子程序3,十进制数ASCII码→二进制
change proc
    mov x,0
    mov cx,[si-1]                    ;成绩的位数→cx
    and cx,000fh                    ;保留低4位 
rept2:
    mov al,[si]                        ;按位取出成绩
    cmp al,30h                        ;是否在0-9之间
    jl exit1
    cmp al,39h
    jg exit1
    and ax,000fh                    ;去掉ASCII码        
    xchg ax,x    
    mov dx,10                        ;将ax中前一次形成的数扩大10倍
    mul dx    
    add x,ax                        ;保存到x
    inc si
    loop rept2
    mov ax,x                        ;按十进制形成的成绩→以二进制保存
    mov score2[di],ax                ;二进制成绩送入score2
    mov x,0    
    add sign2,2                        ;下一个成绩单元
exit1:    ret
change endp
;子程序4 ,按成绩排序
sort proc 
    mov cx,p                             ;数组长度
    dec cx
loop1:push cx                         ;保存外循环次数
    mov bx,0
    mov si,0
loop2:mov ax,score2[bx]
    cmp ax,score2[bx+m+1]             ;m+1=5,下一人的成绩
    jge next                          ;降序
    xchg ax,score2[bx+m+1]             ;交换成绩
    mov score2[bx],ax
    mov al,mingci[si]        
    xchg al,mingci[si+1]             ;交换名次
    mov mingci[si],al
next:add bx,m+1                          ;比较下一个成绩
    inc si
    loop loop2
    pop cx                             ;恢复外循环次数
    loop loop1
    ret
sort endp
;子程序5,打印排序名单
print proc
    lea dx,infor0                    ;显示结果提示
    mov ah,9
    int 21h
    mov cx,p
    mov bx,0
    mov ax,0
    mov di,0
rept3:    
    mov dl,0ah                        ;回车换行
    mov ah,2
    int 21h
    mov dl,0dh
    int 21h
    mov dl,cont                       ;显示名次序号
    mov ah,2
    int 21h
    inc cont
    mov dl,0ah                        ;回车换行
    mov ah,2
    int 21h
    mov dl,0dh
    int 21h
    mov ax,0
    mov al,mingci[di]                ;取名次
    dec al                            ;位置-1,因为地址从0开始
    mov bl,9                        ;姓名位移量ax=al×9(包含$)
    mul bl                            ;乘积在ax
    lea dx,sname
    add dx,ax                        ;偏移地址+姓名位移量
    mov ah,9                        ;显示姓名
    int 21h
    mov dl,0ah                        ;回车换行
    mov ah,2
    int 21h
    mov dl,0dh
    int 21h
    mov ax,0
    mov bx,0
    mov al,mingci[di]                ;取名次
    dec al                            ;地址从0开始
    mov bl,5                        ;成绩位移量=al×5(包含$)
    mul bl
    lea dx,score1
    add dx,ax                        ;偏移地址+成绩位移量
    mov ah,9                        ;显示成绩
    int 21h    
    inc di
   loop rept3
    ret 
print endp
code ends
end start

(2)在 dos 子目录下保存为 7-6.asm,经过汇编 masm 7-6.asm,连接 link 7-6.obj,生成 7-6.exe。

D:\dos〉masm 7-6.asm
D:\dos〉link 7-6.obj
D:\dos〉7-6.exe

(3)运行结果:

  • 1)本例中 p 设为 3,可输入 3 个人的姓名和成绩,更改 p 值可输入多人。
  • 2)程序采用在输入缓冲区中加符号的做法,将每个名字用符号的做法,将每个名字用分隔,每个成绩也用$分隔;在输出时可用 9 号功能直接显示。

3.6 子程序例六

本例子为教材的示例 7-7。

采用多种传参方式能够实现复杂程序的设计要求,同时也应该根据题目来选择寄存器、存储器传参方式。

设计目标

将键入的两个十进制数相加,并显示十进制结果。

设计思路

(1)主程序调用三个子程序。主程序用 JMP 构成循环,可多次做计算;如果按下的不是数字键则退出循环,结束程序;

(2)SUBR1 子程序 1:功能为键盘输入,数字键 ASCII 码 → 十进制数(该十进制数保存为二进制),用存储单元 X 传参;

(3)SUBR2 子程序 2:功能为两数相加,以寄存器 BX 传参;

(4)SUBR3 子程序 3:功能为显示十进制数。先将二进制数→十进制数。将传参寄存器 BX 中的二进制数用除以 10 取余数的方法转换为十进制数,再将余数加 30H 变为十进制数的 ASCII 码,然后显示。

实验步骤

(1)双击桌面上的记事本 gedit,录入下列程序:

;7-7.asm  寄存器、存储器传参。键入两个十进制数相加,并显示十进制结果。
data segment
  x dw ?,?
  cc1 db 0ah,0dh,'x1=$'
  cc2 db 0ah,0dh,'x2=$'
  cc3 db 0ah,0dh,'y=x1+x2=$'
data ends
code segment 
assume cs:code,ds:data
start:
mov ax,data
mov ds,ax
;主程序
main proc far
mov cx,0
mov bx,0
mov si,0
mov dx,offset cc1                ;显示提示1
mov ah,9
int 21h
call subr1                    ;输入第1个数
mov dx,offset cc2                ;显示提示2
mov ah,9
int 21h    
mov bx,0
mov cx,0
mov si,2
call subr1                    ;输入第2个数
call subr2                    ;相加
mov dx,offset cc3                ;显示提示3
mov ah,9
int 21h    
call subr3                    ;显示结果
jmp main
out1:                            ;结束
mov ah,4ch
int 21h
main endp
;子程序1:键盘输入、数字键ASCII码→十进制数(以二进制保存)
subr1 proc near
mov ah,1                        ;键盘输入十进制数
int 21h
cmp al,0dh                    ;回车?
jz exit
cmp al,'0'                    ;其它字符?
jl out1                        ;是其它字符转out1,退出,结束。
cmp al,'9'
jg out1    
and ax,000fh                    ;形成十进制数(以二进制保存)
xchg ax,bx                            
mov cx,10
mul cx                        ;乘以10 → 十位、百位
add bx,ax        
jmp subr1
exit:cmp cx,0                    ;先键入了回车,退出
jz out1
mov x[si],bx                    ;存储单元x传参
ret 
subr1 endp
;子程序2,两数相加
subr2 proc near
mov bx,x
add bx,x+2                    ;寄存器BX传参
ret
subr2 endp
;子程序3,显示十进制数。将二进制数→十进制数
subr3 proc near
mov ax,bx
mov cx,0
mov bx,10                        ;将ax变为十进制数
let1:
mov dx,0
inc cx
idiv bx                        ;除以10,取余数
push dx                        ;保存余数
cmp ax,0
jnz let1
let2:                            ;显示结果
pop ax                        ;将余数弹入ax
add ax,0030h                    ;余数调整为ascii码
mov dl,al
mov ah,2                        ;显示
int 21h
loop let2
ret
subr3 endp
code ends
end start

(2)在 dos 子目录下保存为 7-7.asm,经过汇编 masm 7-7.asm,连接 link 7-7.obj,生成 7-7.exe。

D:\dos〉masm 7-7.asm
D:\dos〉link 7-7.obj
D:\dos〉7-7.exe

(3)运行结果:

**实验结果分析**:

(1)该程序要求输入正数,运算结果的最大值不能超过 65535,即 16 位寄存器保存无符号数的范围。

(2)输入的两个十进制数的位数可以不一样。

(3)如果输入负的十进制数,程序应该怎样编写?(可参考第 8 章示例 8-4)

4. 子程序与模块化

本节实验取自教材中第七章的《实例七 子程序与模块化》

实验目的

通过分析和运行示例程序,掌握子程序设计思路和技巧。根据子程序传参方法,尝试实现较复杂的程序功能。

实验内容

参考示例 7-7,完成下列实验内容:

  1. 实现两个键入的十进制数相减运算。(提示:如果结果为负数,需要求绝对值)
  2. 实现两个键入的十进制数相乘运算。(提示:要考虑结果溢出问题)
  3. 实现两个键入的十进制数相除运算。(提示:分别显示商和余数)
  4. 键入一个十六进制数,求其真值(用十进制显示,负数前加负号“-”)(7-2b.asm)

实验要求

  1. 4 个题目可任选 2 个
  2. 实验内容用截图形式记录实验结果
  3. 写出实验结果分析

实验拓展

设计一个小计算器。用菜单选择计算功能。下一章节

类似文章