《汇编语言》王爽(第四版) 课程设计1

奶酪博士 2024-07-07 16:35:01 阅读 98

文章目录

前言

一、课程设计任务

二、任务分析

1.公司数据的格式

2.数据转为字符串

3.显示多个数据

三、实现代码

总结


前言

本文是王爽老师《汇编语言》(第四版) 课程设计1 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。这是目前写的最综合的程序,要用到实验七以及实验十中写的程序。

一、课程设计任务

实验任务:将  实验7 寻址方式在结构化数据访问中的应用 中提供的公司数据,呈现在屏幕上,效果如下。

二、任务分析

整体思路分析:既然数据是实验七中给定的,那么这里就可以利用实验七中已经写好的功能“将公司数据按照格式写到table中”,将table中的公司数据逐个转为字符串,并显示在屏幕上。

下面分步实现。

1.公司数据的格式

在 实验7 寻址方式在结构化数据访问中的应用 中,已经实现的功能是将data段中给出的公司数据按照指定格式存储到table段中。但是那时候还没有学习到call指令和ret指令,于是只能用jmp指令进行了类似函数的封装设计。

现在可以把当初的代码改为子程序调用的形式,也就是写一个通用的子程序totable,完成data段中数据按格式填写到table段中的功能。而在totable子程序中,又可以写一个copy1子程序,用于复制年份、年收入、雇员人数的数据。

totable子程序代码如下。

<code>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 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。通过这个课程设计,能够复习学过的几乎所有汇编语言知识,还增强了对子程序的理解及运用能力!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。