《汇编语言》王爽(第四版) 课程设计1
本文是王爽老师《汇编语言》(第四版) 课程设计1 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。这是目前写的最综合的程序。
文章目录
前言
本文是王爽老师《汇编语言》(第四版) 课程设计1 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。这是目前写的最综合的程序,要用到实验七以及实验十中写的程序。
一、课程设计任务
实验任务:将 实验7 寻址方式在结构化数据访问中的应用 中提供的公司数据,呈现在屏幕上,效果如下。
二、任务分析
整体思路分析:既然数据是实验七中给定的,那么这里就可以利用实验七中已经写好的功能“将公司数据按照格式写到table中”,将table中的公司数据逐个转为字符串,并显示在屏幕上。
下面分步实现。
1.公司数据的格式
在 实验7 寻址方式在结构化数据访问中的应用 中,已经实现的功能是将data段中给出的公司数据按照指定格式存储到table段中。但是那时候还没有学习到call指令和ret指令,于是只能用jmp指令进行了类似函数的封装设计。
现在可以把当初的代码改为子程序调用的形式,也就是写一个通用的子程序totable,完成data段中数据按格式填写到table段中的功能。而在totable子程序中,又可以写一个copy1子程序,用于复制年份、年收入、雇员人数的数据。
totable子程序代码如下。
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
stack segment
dw 32 dup(0) ;64个字节.32个push位
stack ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
start:
mov ax,data ;设置ds段地址
mov ds,ax
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,40H
mov ax,table ;设置es段为table段
mov es,ax
call totable ;调用子程序,将data段中21年的数据复制到table段中
mov ax,4c00H ;程序返回
int 21H
totable: ;功能:将data段中给定的21年的数据按照指定格式写到table段中
;参数:无
;返回:table段中按格式写入了21年的数据(年份、年总收入、雇员人数、人均收入)
push di ;将用到的寄存器压入栈
push si
push bp
push ax
push bx
push cx
push dx
;复制 年份 数据
mov di,0 ;data段当前该复制的数据的偏移地址,设置初始值为data段数据的首地址
mov si,0 ;索引,table段(es段)当前年份的数据的首地址
mov bp,4 ;每年的数据所占的字节数,即内循环的次数;
call copy1 ;调用子程序,复制数据
;复制 年总收入
mov si,5 ;索引,table段(es段)当前年份的数据的首地址
mov bp,4 ;每年的数据所占的字节数,即内循环的次数;
call copy1
;复制 雇员人数
mov si,0aH ;索引,table段(es段)当前年份的数据的首地址
mov bp,2 ;每年的数据所占的字节数,即内循环的次数;
call copy1
;计算 人均收入
mov bx,0 ;第N年
mov si,0dH ;索引,table段(es段)当前年份的数据的首地址
mov cx,21 ;共有N年
totable2:
mov ax,es:[bx+5] ;取年总收入的低16位
mov dx,es:[bx+7] ;取年总收入的高16位
div word ptr es:[bx+0aH] ;除法运算,人均收入不超过65535,可以直接运算
mov es:[bx+si],ax ;商(即结果取整)写入table段
add bx,10H ;年份索引+1,切换到下一年
loop totable2 ;计算下一年的数据
pop dx ;将寄存器的值pop出来
pop cx
pop bx
pop ax
pop bp
pop si
pop di
ret
copy1: ;功能:将N年的某项数据按照指定格式复制到table段中
;参数:cx 年份的个数,即外循环的次数;
; bp 每年的数据所占的字节数,即内循环的次数;
; bx 索引,table段中第N个年份
; di data段当前该复制的数据的偏移地址
; si 索引,table段(es段)当前年份的数据的首地址
;返回:table段指定数据项N年的数据 按照格式填写完成
; di 索引,data段当前该复制的数据的偏移地址(因而di不用压入栈)
push bp ;将用到的寄存器压入栈
push bx
push si
push ax ;cx会在copy2即外循环中压入栈
mov bx,0 ;bx = 第N个年份的数据
mov cx,21 ;cx = 外循环的次数
copy2:
push cx ;将外层循环的次数压入栈
mov cx,bp ;cx = bp = 每年的数据所占的字节数,即内循环的次数;
copy3:
mov al,ds:[di] ;将data段中的字节复制到table段中
mov es:[bx+si],al
inc si ;table段中当前字节索引+1
inc di ;data段中当前字节索引+1
loop copy3
add bx,10H ;年份索引+1,即table段中切换到下一年的数据
sub si,bp ;table段中当前字节索引回退到初始值
pop cx ;pop出外层循环计数器
loop copy2 ;进行下一年的数据复制
pop ax ;程序返回前将寄存器的值pop出来
pop si
pop bx
pop bp
ret
code ends
end start
2.数据转为字符串并显示
以上程序段已经实现的功能是将给定数据按照格式写到table段中。接下来要做的就是,①将这些数据转为对应的十进制字符串形式;②在屏幕上显示出这些数据。
先来考虑只显示一个数据的情况。
这个将内存中的数据转为十进制字符串并进行显示的功能,之前在实验十中已经实现过。但是这次要显示的一些数据超过了65535,也就超过了word型的范围。而要显示这些数据,原先在 实验10 编写3个子程序 中的写的(子程序3)在屏幕指定位置显示word型数据的子程序已经不能满足需要,于是这里就写一个新的通用的子程序,用于显示dword型数据。正好我也发现,之前在实验10中写的子程序有一些不足之处,借着这个机会重写一遍,作为练习。
子程序代码如下。
assume cs:code,ss:stack
stack segment
dw 16 dup(0) ;16个push位
stack ends
str segment
dw 0
str ends
code segment
start:
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,20H
mov ax,str ;设置ds指向str段
mov ds,ax
mov si,0
mov dx,004FH ;要显示的dword型数据的高16位
mov ax,5DA2H ;要显示的dword型数据的低16位
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,8 ;在屏幕第几行开始显示
mov dl,5 ;在屏幕第几列开始显示
mov cl,2 ;显示的字符颜色
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
mov ax,4c00H
int 21H
dtoc_dword:
;功能:将dword型数据转为十进制,存入str段中
;参数:ds指向str段,si指向在str段的哪个地址开始存
;ax存放dword型数据的低16位,dx存放dword型数据的高16位
;返回:ds:si指向str段十进制数据的首地址
push cx ;将用到的寄存器压入栈
push bx
push si
push ax
push dx
mov bx,0 ;bx = 压入栈的余数的个数
pushyushu:
mov cx,000aH ;cx = 除数 = 10
call divdw ;调用子程序进行除法计算,返回值:商低16位在ax,高16位在dx,余数在cx
push cx ;将余数压入栈
inc bx ;压入栈的余数个数+1
mov cx,ax
add cx,dx ;商的高低16位必然都是非负数,如果和为0,那么说明商为0,则除法进行完毕
jcxz popyushu ;若除法进行完毕,则转去将栈中余数倒序pop出来
jmp pushyushu ;否则,就再进行一次除法
popyushu: ;将栈中余数倒序pop出来,存入str段
mov cx,bx ;如果循环次数剩余0,就退出循环
jcxz dtoc_over
pop ax ;取出一个余数
add ax,30H ;转为数字对应的字符
mov ds:[si],ax ;将该余数存入str段内存中
inc si
dec bx ;循环次数-1
loop popyushu ;再继续取余数,转存到str段
dtoc_over:
inc si ;都存完以后,再存个0到str段,作为结尾符
mov byte ptr ds:[si],0
pop dx ;将寄存器的值pop出来
pop ax
pop si
pop bx
pop cx
ret
divdw: ;功能:计算dword型被除数与word型除数的除法
;参数: ax=被除数低16位,dx=被除数高16位,cx = 除数
;返回: ax=商的低16位,dx=商的高16位,cx = 余数
;计算公式: X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N
;其中X为被除数,N为除数,H为被除数的高16位,L为被除数的低16位,
;int()表示结果的商,rem()表示结果的余数。
;思路是分左右两项分别计算,然后再求和。
push bx ;bx是额外用到的寄存器,要压入栈
mov bx,ax ;bx=L
mov ax,dx ;ax = dx = H
mov dx,0 ;要计算的是H/N,H和N都是16位,但CPU只能计算16/8位,因此让高位dx=0
div cx ;计算H/N,结果的商即int(H/N)保存在ax,余数即rem(H/N)保存在dx
;接下来要计算int(H/N)*65536,即ax * 65536
;思考一下,65536就是0001 0000 H,
;因此计算结果就是,高16位=int(H/N)=ax,低16位为0000H。
push ax ;将int(H/N)*65536结果的高16位,即int(H/N),压入栈
mov ax,0
push ax ;将int(H/N)*65536结果的低16位,即0000H,压入栈
;至此,左边项已计算完毕,且高低16位已先后入栈。
;接下来要计算 rem(H/N)*65536 ,同理可得,
;计算结果为 高16位= rem(H/N) ,即此时dx的值,
;低16位为 0000H。
mov ax,bx ;ax = bx = L ,而rem(H/N)*65536的低16位=0,
;因此ax = bx = 即 [rem(H/N)*65536 + L]的低16位
;此时dx = rem(H/N) = rem(H/N)*65536的高16位 = [rem(H/N)*65536 + L]高16位
div cx ;计算 [rem( H/N) * 65536 + L]/N ,结果的商保存在ax,余数保存在dx
;至此,右边项计算完毕,商在ax中,余数在dx中。
;接下来要将两项求和。 左边项的高、低16位都在栈中,
;其中高16位就是最终结果的高16位,低16位是0000H。
;右边项的商为16位,在ax中,也就是最终结果的低16位,
;余数在dx中,也就是最终结果的余数。
mov cx,dx ;cx = 最终结果的余数
pop bx ;bx = int(H/N)*65536结果的低16位,即0000H。
pop dx ;dx = int(H/N)*65536结果的高16位,即最终结果的高16位
pop bx ;还原寄存器的值
ret
show_str:
;功能:将str段中首地址为ds:si的字符,以指定颜色显示在屏幕指定位置
;参数:dh 行号, dl 列号 ,cl 颜色,ds指向str段,si指向字符串首地址
;返回:无
push dx ;将子程序用到的寄存器压入栈
push si
push es
push cx
push ax
push bx
mov ax,0B800H ;设置es为显示区段地址
mov es,ax
mov ax,00A0H ;设置首字符显示的地址
mul dh
mov dh,0
add ax,dx
add ax,dx
mov bx,ax ;bx是显示区的偏移地址
mov al,cl ;用al存储属性字节
mov ch,0
mov si,0
show2: ;循环读取字符并显示
mov cl,ds:[si]
jcxz showpop ;若读到0,就退出循环
mov es:[bx],cl
inc bx
mov es:[bx],al
inc bx
inc si
jmp short show2
showpop: ;将寄存器的值pop出来
pop bx
pop ax
pop cx
pop es
pop si
pop dx
ret ;返回
code ends
end start
3.显示多个数据
上边写的子程序,可以用来显示一个数据(在ax、dx中给出)。而现在需要显示多个数据,考虑在主程序中采用循环的方式多次调用用于显示数据的子程序。其中要注意根据数据项和年份来切换在屏幕显示的行号和列号。
代码如下。
assume cs:code,ss:stack
data segment ;这些是要写入的数据
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
stack segment
dw 32 dup(0) ;64个字节.32个push位
stack ends
table segment ;将data段中给定数据按照格式填写到这个table段中
db 21 dup ('year summ ne ?? ')
table ends
str segment
dw 8 dup(0) ;16个字节,将每一个数据转为十进制字符形式,存到这里
dw 0 ;16个字节,存放指定的屏幕显示行号字节、列号字节、字符属性字节
str ends
code segment
start:
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,40H
mov ax,data ;设置ds段指向data段
mov ds,ax
mov ax,table ;设置es段指向table段
mov es,ax
call totable ;调用子程序,将data段中21年的数据复制到table段中
mov bx,str ;设置ds指向str段
mov ds,bx
mov dl,03H ;指定在屏幕上开始显示的行号
mov ds:[10H],dl ;存到str段中
mov dl,05H ;指定在屏幕上开始显示的列号
mov ds:[11H],dl ;存到str段中
mov cl,2 ;显示的字符的属性字节
mov ds:[12H],cl ;存到str段中
call show_table ;将table段中的数据显示到屏幕指定位置上
mov ax,4c00H ;程序返回
int 21H
show_table: ;功能:将table段中的数据按照格式显示到屏幕的指定位置上
;参数:ds指向str段,es指向table段
; 显示指定的行号、列号、字符数形在str段第10H-12H中
;返回:无
push bx ;将寄存器的值压入栈
push si
push di
push cx
push ax
push dx
mov bx,0 ;table段中当年数据的起始地址
mov si,0 ;转换成字符后的数据从str哪个地址开始存
mov cx,21 ;一共21年,所以循环21次
thisyear:
call show_table2
loop thisyear ;这一年的显示完成,继续显示下一年的数据
pop dx ;将寄存器的值pop出来
pop ax
pop cx
pop di
pop si
pop bx
ret
;这是个show_table内部的函数
show_table2: ;通过循环,将table段中的数据显示到屏幕上
push cx ;将寄存器的值压入栈,此时cx是循环次数,代表当前是第几年
mov di,0 ;table段中当前该存每年第N个字节的数据
mov ax,es:[bx+di] ;取年份数据的前两个字节的数据
mov ds:[0],ax ;将年份数据存入str段
add di,2
mov dx,es:[bx+di] ;取年份数据的高两个字节的数据
add di,2
mov ds:[2],dx
mov byte ptr ds:[4],0 ;以0作为结尾符
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di
mov ax,es:[bx+di] ;取年总收入的低16位数据
add di,2
mov dx,es:[bx+di] ;取年总收入的高16位数据
add di,2
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,0aH ;上一项数据占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di ;复制 雇员人数
mov ax,es:[bx+di]
add di,2
mov dx,0
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,14H ;上一项数据再占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di ;计算 人均收入
mov ax,es:[bx+di]
add di,2
mov dx,0
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,1EH ;上一项数据再占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
add bx,0010H ;切换到下一年,table段中的下一行
mov cl,ds:[10H] ;取出当前行号
inc cl ;将行号+1,因为要在屏幕下一行写下一年的数据
mov ds:[10H],cl
pop cx ;将寄存器的值pop出来
ret ;这一年的显示完成,继续显示下一年的数据
code ends
end start
三、实现代码
最终的完整代码如下!工程竣工~
assume cs:code,ss:stack
data segment ;这些是要写入的数据
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
stack segment
dw 32 dup(0) ;64个字节.32个push位
stack ends
table segment ;将data段中给定数据按照格式填写到这个table段中
db 21 dup ('year summ ne ?? ')
table ends
str segment
dw 8 dup(0) ;16个字节,将每一个数据转为十进制字符形式,存到这里
dw 8 dup(0) ;16个字节,存放指定的屏幕显示行号字节、列号字节、字符属性字节
str ends
code segment
start:
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,40H
mov ax,data ;设置ds段指向data段
mov ds,ax
mov ax,table ;设置es段指向table段
mov es,ax
call totable ;调用子程序,将data段中21年的数据复制到table段中
mov bx,str ;设置ds指向str段
mov ds,bx
mov dl,03H ;指定在屏幕上开始显示的行号
mov ds:[10H],dl ;存到str段中
mov dl,05H ;指定在屏幕上开始显示的列号
mov ds:[11H],dl ;存到str段中
mov cl,2 ;显示的字符的属性字节
mov ds:[12H],cl ;存到str段中
call show_table ;将table段中的数据显示到屏幕指定位置上
mov ax,4c00H ;程序返回
int 21H
show_table: ;功能:将table段中的数据按照格式显示到屏幕的指定位置上
;参数:ds指向str段,es指向table段
; 显示指定的行号、列号、字符数形在str段第10H-12H中
;返回:无
push bx ;将寄存器的值压入栈
push si
push di
push cx
push ax
push dx
mov bx,0 ;table段中当年数据的起始地址
mov si,0 ;转换成字符后的数据从str哪个地址开始存
mov cx,21 ;一共21年,所以循环21次
thisyear:
call show_table2
loop thisyear ;这一年的显示完成,继续显示下一年的数据
pop dx ;将寄存器的值pop出来
pop ax
pop cx
pop di
pop si
pop bx
ret
;这是个show_table内部的函数
show_table2: ;通过循环,将table段中的数据显示到屏幕上
push cx ;将寄存器的值压入栈,此时cx是循环次数,代表当前是第几年
mov di,0 ;table段中当前该存每年第N个字节的数据
mov ax,es:[bx+di] ;取年份数据的前两个字节的数据
mov ds:[0],ax ;将年份数据存入str段
add di,2
mov dx,es:[bx+di] ;取年份数据的高两个字节的数据
add di,2
mov ds:[2],dx
mov byte ptr ds:[4],0 ;以0作为结尾符
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di
mov ax,es:[bx+di] ;取年总收入的低16位数据
add di,2
mov dx,es:[bx+di] ;取年总收入的高16位数据
add di,2
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,0aH ;上一项数据占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di ;复制 雇员人数
mov ax,es:[bx+di]
add di,2
mov dx,0
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,14H ;上一项数据再占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
inc di ;计算 人均收入
mov ax,es:[bx+di]
add di,2
mov dx,0
call dtoc_dword ;调用子程序,将dword型数据转为十进制,存入str段中指定位置
mov dh,ds:[10H] ;从str段中取出指定的显示起始行号
mov dl,ds:[11H] ;从str段中取出指定的显示起始列号
add dl,1EH ;上一项数据再占10列
mov cl,ds:[12H] ;从str段中取出指定的显示字符的属性字节
call show_str ;调用子程序,将str段中的十进制数据显示在屏幕指定位置
add bx,0010H ;切换到下一年,table段中的下一行
mov cl,ds:[10H] ;取出当前行号
inc cl ;将行号+1,因为要在屏幕下一行写下一年的数据
mov ds:[10H],cl
pop cx ;将寄存器的值pop出来
ret ;这一年的显示完成,继续显示下一年的数据
dtoc_dword: ;功能:将dword型数据转为十进制,存入str段中
;参数:ds指向str段,si指向在str段的哪个地址开始存
;ax存放dword型数据的低16位,dx存放dword型数据的高16位
;返回:ds:si指向str段十进制数据的首地址
push cx ;将用到的寄存器压入栈
push bx
push si
push ax
push dx
mov bx,0 ;bx = 压入栈的余数的个数
pushyushu:
mov cx,000aH ;cx = 除数 = 10
call divdw ;调用子程序进行除法计算,返回值:商低16位在ax,高16位在dx,余数在cx
push cx ;将余数压入栈
inc bx ;压入栈的余数个数+1
mov cx,ax
add cx,dx ;商的高低16位必然都是非负数,如果和为0,那么说明商为0,则除法进行完毕
jcxz popyushu ;若除法进行完毕,则转去将栈中余数倒序pop出来
jmp pushyushu ;否则,就再进行一次除法
popyushu: ;将栈中余数倒序pop出来,存入str段
mov cx,bx ;如果循环次数剩余0,就退出循环
jcxz dtoc_over
pop ax ;取出一个余数
add ax,30H ;转为数字对应的字符
mov ds:[si],ax ;将该余数存入str段内存中
inc si
dec bx ;循环次数-1
loop popyushu ;再继续取余数,转存到str段
dtoc_over:
inc si ;都存完以后,再存个0到str段,作为结尾符
mov byte ptr ds:[si],0
pop dx ;将寄存器的值pop出来
pop ax
pop si
pop bx
pop cx
ret
divdw: ;功能:计算dword型被除数与word型除数的除法
;参数: ax=被除数低16位,dx=被除数高16位,cx = 除数
;返回: ax=商的低16位,dx=商的高16位,cx = 余数
;计算公式: X/N = int( H/N ) * 65536 + [rem( H/N) * 65536 + L]/N
;其中X为被除数,N为除数,H为被除数的高16位,L为被除数的低16位,
;int()表示结果的商,rem()表示结果的余数。
;思路是分左右两项分别计算,然后再求和。
push bx ;bx是额外用到的寄存器,要压入栈
mov bx,ax ;bx=L
mov ax,dx ;ax = dx = H
mov dx,0 ;要计算的是H/N,H和N都是16位,但CPU只能计算16/8位,因此让高位dx=0
div cx ;计算H/N,结果的商即int(H/N)保存在ax,余数即rem(H/N)保存在dx
;接下来要计算int(H/N)*65536,即ax * 65536
;思考一下,65536就是0001 0000 H,
;因此计算结果就是,高16位=int(H/N)=ax,低16位为0000H。
push ax ;将int(H/N)*65536结果的高16位,即int(H/N),压入栈
mov ax,0
push ax ;将int(H/N)*65536结果的低16位,即0000H,压入栈
;至此,左边项已计算完毕,且高低16位已先后入栈。
;接下来要计算 rem(H/N)*65536 ,同理可得,
;计算结果为 高16位= rem(H/N) ,即此时dx的值,
;低16位为 0000H。
mov ax,bx ;ax = bx = L ,而rem(H/N)*65536的低16位=0,
;因此ax = bx = 即 [rem(H/N)*65536 + L]的低16位
;此时dx = rem(H/N) = rem(H/N)*65536的高16位 = [rem(H/N)*65536 + L]高16位
div cx ;计算 [rem( H/N) * 65536 + L]/N ,结果的商保存在ax,余数保存在dx
;至此,右边项计算完毕,商在ax中,余数在dx中。
;接下来要将两项求和。 左边项的高、低16位都在栈中,
;其中高16位就是最终结果的高16位,低16位是0000H。
;右边项的商为16位,在ax中,也就是最终结果的低16位,
;余数在dx中,也就是最终结果的余数。
mov cx,dx ;cx = 最终结果的余数
pop bx ;bx = int(H/N)*65536结果的低16位,即0000H。
pop dx ;dx = int(H/N)*65536结果的高16位,即最终结果的高16位
pop bx ;还原寄存器的值
ret
show_str: ;功能:将str段中首地址为ds:si的字符,以指定颜色显示在屏幕指定位置
;参数:dh 行号, dl 列号 ,cl 颜色,
; ds指向str段,si指向str段要显示的字符串首地址
;返回:无
push dx ;将子程序用到的寄存器压入栈
push si
push es
push cx
push ax
push bx
mov ax,0B800H ;设置es为显示区段地址
mov es,ax
mov ax,00A0H ;设置首字符显示的地址
mul dh
mov dh,0
add ax,dx
add ax,dx
mov bx,ax ;bx是显示区的偏移地址
mov al,cl ;用al存储属性字节
mov ch,0
mov si,0
show2: ;循环读取字符并显示
mov cl,ds:[si]
jcxz showpop ;若读到0,就退出循环
mov es:[bx],cl
inc bx
mov es:[bx],al
inc bx
inc si
jmp short show2
showpop: ;将寄存器的值pop出来
pop bx
pop ax
pop cx
pop es
pop si
pop dx
ret ;返回
totable: ;功能:将data段中给定的21年的数据按照指定格式写到table段中
;参数:无
;返回:table段中按格式写入了21年的数据(年份、年总收入、雇员人数、人均收入)
push di ;将用到的寄存器压入栈
push si
push bp
push ax
push bx
push cx
push dx
;复制 年份 数据
mov di,0 ;data段当前该复制的数据的偏移地址,设置初始值为data段数据的首地址
mov si,0 ;索引,table段(es段)当前年份的数据的首地址
mov bp,4 ;每年的数据所占的字节数,即内循环的次数;
call copy1 ;调用子程序,复制数据
;复制 年总收入
mov si,5 ;索引,table段(es段)当前年份的数据的首地址
mov bp,4 ;每年的数据所占的字节数,即内循环的次数;
call copy1
;复制 雇员人数
mov si,0aH ;索引,table段(es段)当前年份的数据的首地址
mov bp,2 ;每年的数据所占的字节数,即内循环的次数;
call copy1
;计算 人均收入
mov bx,0 ;第N年
mov si,0dH ;索引,table段(es段)当前年份的数据的首地址
mov cx,21 ;共有N年
totable2:
mov ax,es:[bx+5] ;取年总收入的低16位
mov dx,es:[bx+7] ;取年总收入的高16位
div word ptr es:[bx+0aH] ;除法运算,人均收入不超过65535,可以直接运算
mov es:[bx+si],ax ;商(即结果取整)写入table段
add bx,10H ;年份索引+1,切换到下一年
loop totable2 ;计算下一年的数据
pop dx ;将寄存器的值pop出来
pop cx
pop bx
pop ax
pop bp
pop si
pop di
ret
copy1: ;功能:将N年的某项数据按照指定格式复制到table段中
;参数:cx 年份的个数,即外循环的次数;
; bp 每年的数据所占的字节数,即内循环的次数;
; bx 索引,table段中第N个年份
; di data段当前该复制的数据的偏移地址
; si 索引,table段(es段)当前年份的数据的首地址
;返回:table段指定数据项N年的数据 按照格式填写完成
; di 索引,data段当前该复制的数据的偏移地址(因而di不用压入栈)
push bp ;将用到的寄存器压入栈
push bx
push si
push ax ;cx会在copy2即外循环中压入栈
mov bx,0 ;bx = 第N个年份的数据
mov cx,21 ;cx = 外循环的次数
copy2:
push cx ;将外层循环的次数压入栈
mov cx,bp ;cx = bp = 每年的数据所占的字节数,即内循环的次数;
copy3:
mov al,ds:[di] ;将data段中的字节复制到table段中
mov es:[bx+si],al
inc si ;table段中当前字节索引+1
inc di ;data段中当前字节索引+1
loop copy3
add bx,10H ;年份索引+1,即table段中切换到下一年的数据
sub si,bp ;table段中当前字节索引回退到初始值
pop cx ;pop出外层循环计数器
loop copy2 ;进行下一年的数据复制
pop ax ;程序返回前将寄存器的值pop出来
pop si
pop bx
pop bp
ret
code ends
end start
总结
本文是王爽老师《汇编语言》(第四版) 课程设计1 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。通过这个课程设计,能够复习学过的几乎所有汇编语言知识,还增强了对子程序的理解及运用能力!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)