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

这篇具有很好参考价值的文章主要介绍了《汇编语言》王爽(第四版) 课程设计1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文章目录

前言

一、课程设计任务

二、任务分析

1.公司数据的格式

2.数据转为字符串

3.显示多个数据

三、实现代码

总结


前言

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

一、课程设计任务

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

汇编语言课程设计1,汇编语言,开发语言

二、任务分析

整体思路分析:既然数据是实验七中给定的,那么这里就可以利用实验七中已经写好的功能“将公司数据按照格式写到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 “将实验七中给定的公司数据显示在屏幕上”的分析及代码。通过这个课程设计,能够复习学过的几乎所有汇编语言知识,还增强了对子程序的理解及运用能力!文章来源地址https://www.toymoban.com/news/detail-537814.html

到了这里,关于《汇编语言》王爽(第四版) 课程设计1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式:ARM汇编语言程序设计基础教程

    ① 合理地分配存储器资源,将前述的目标系统‘数据结构模型’表示到各存储器单元。 ② CPU寄存器数量有限,在程序中,大多数操作都要使用寄存器;并且有的操作使用特定的寄存器(如堆栈操作使用SP/R13等),程序中要合理分配各寄存器的用途。 用计算机语言,对数据结

    2023年04月23日
    浏览(89)
  • 南京邮电大学汇编语言程序设计实验二(用户登录验证程序的设计)

    1.掌握循环程序的编写以及结束循环的方法。 2.掌握DOS、BIOS功能调用的使用方法。 用户登录验证程序的实现 程序执行后,给出提示操作,请用户键入用户名和密码;用户在键入密码时,程序不回显键入字符;只有当用户键入的用户名,密码字符串和程序内定的字符串相同时

    2023年04月18日
    浏览(57)
  • 基于Proteus仿真的交通信号灯设计——利用汇编语言实现

    基本信息 采用AT89C51单片机 晶振频率:12MHZ 红绿灯:发光二极管 数字显示:LED数码管 东西南北四个方向,一次循环共有六个状态,且每个状态的倒计时时间一样 交通状态 东西红灯22s,南北绿灯22s(数码管从0到21); 东西红灯5s,南北绿灯5s且没0.5s闪烁一次(数码管从0到4);

    2024年02月09日
    浏览(56)
  • 实验一8086计CPU系统寻址方式和汇编语言程序设计

    实验一8086计CPU系统寻址方式和汇编语言程序设 一、实验目的 (1)掌握8086CPU系统的逻辑地址和寻址方式。 (2)掌握8086CPU系统中机器数的表示方式。 (3)掌握指令的机器码表示方法。 (4)掌握堆栈的概念和操作过程。 (5)掌握集成开发环境下的程序设计和调试方法。 (6)掌握汇编语言实

    2023年04月22日
    浏览(49)
  • 汇编语言实验8:BIOS/DOS功能调用与宏指令程序设计

    掌握汇编语言程序设计的基本方法和技能 掌握汇编语言源程序的编辑汇编连接和执行的完整过程 通过上机操作理解宏定义、宏调用、宏展开的概念,熟练运用宏功能编写程序 掌握BIOS/DOS基础功能的实现调用方法 理解常用的DOS功能调用的基本使用,能熟练运用1号,2号,9号,

    2024年02月03日
    浏览(67)
  • 浙大版《C语言程序设计》第四版(何钦铭颜晖) 第7章 数组 课后习题答案

    你也可以上程序咖(https://meta.chengxuka.com),打开大学幕题板块,不但有答案,讲解,还可以在线答题。 一、选择题 1.假定 int 类型变量占用两个字节,则以下定义的数组 a 在内存中所占字节数是( )。 A. 20 B.10 C.6 D.3 答:A 解析:题目中,根据 int a[10] ,表示定义了数组的长度

    2023年04月18日
    浏览(60)
  • 汇编语言笔记(一)——汇编语言基础

    一、开发环境 我使用visual studio 2022 preview,其他版本的设置大同小异。 第一步: 打开visual studio,点击“创建新项目”: 第二步: visual studio并没有专门的汇编项目,所以需要挂羊头卖狗肉,选择C++空项目 第三步: 输入项目名称,点击创建 第四步: 鼠标右键单击项目名称—

    2024年02月05日
    浏览(45)
  • 汇编语言—常见汇编指令汇总

    mov    寄存器 ,数据                如:mov ax ,8 mov   寄存器,寄存器              如:mov ax,bx mov   寄存器,内存单元          如:mov ax,[0] mov   内存单元,寄存器          如:mov [0],ax mov   段寄存器,寄存器          如:mov ds,ax add   寄存器,数据

    2024年02月10日
    浏览(44)
  • 5.8 汇编语言:汇编高效除法运算

    通常情况下计算除法会使用 div/idiv 这两条指令,该指令分别用于计算无符号和有符号除法运算,但除法运算所需要耗费的时间非常多,大概需要比乘法运算多消耗10倍的CPU时钟,在Debug模式下,除法运算不会被优化,但Release模式下,除法运算指令会被特定的算法经过优化后转

    2024年02月11日
    浏览(44)
  • 5.7 汇编语言:汇编高效乘法运算

    乘法指令是一种在CPU中实现的基本算术操作,用于计算两个数的乘积。在汇编语言中,乘法指令通常是通过 mul(无符号乘法) 和 imul(有符号乘法) 这两个指令实现的。由于乘法指令在执行时所消耗的时钟周期较多,所以编译器在优化代码时通常会尝试将乘法操作转换为更

    2024年02月11日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包