加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

汇编语言(王爽第三版)课程设计1

(2017-04-15 18:14:29)
标签:

汇编

王爽

it

教育

分类: 汇编语言(王爽第三版)实验

课程设计1

 

任务:将实验七中的Power idea 公司的数据,按照图10.2所示的格式在屏幕中显示出来。

实验目的:

       1)进一步熟悉掌握子程序的编写。理解入口参数,返回值,程序目的。

       2)在主程序中利用子程序的入口参数,调用子程序。

       3)进一步熟悉内存的寻址。

       4)优化原先编写的几个子程序,注意标号、寄存器的保护等。

       5)将以前所学习的到的几个实验的程序,部分修改后,合理的运用它们;也进一步了解什么是子程序,子程序的调用,子程序的参数,返回值。

编程分析:

       1)讨论数据的存储问题,我们除非接触到数据库这种东东,在汇编语言或C语言中,存储数据(指有用的数据,例如统计信息等)的方式不外乎将数据存储在磁盘中,生成特定的文件,然后需要时将这些数据装载到内存中,通过程序进行读取。

       Power idea 公司的数据直接就将数据存储在了汇编文件中,这样在执行可执行程序时,直接就将数据装载到了内存中。省去了从数据文件中读取数据的过程了。

       这种存储数据的方式不太灵活,需要修改数据时,还必须重新修改汇编源文件,重新编译、链接。

       2)在实验七中,我们已经实现了将这些数据写入到了table段内存中;目前我们考虑的就是将table段内存怎样读取出来后,有的数据不是字符串,需要将数字转换成字符串形式;写入一个目标内存段中(此设计中是指的显存段中)。我们直接将实验七的程序改造成一个子程序即可,在主程序中直接调用,那么table段内存中就是我们程序员所期望的存储模式了。

       3table段数据的读取并写入一个临时的内存存储段中。data段:对于字符串(例如:年份),我们直接写入目标内存,并加入0标记;对于其他的数值,我们调用实验10中的dtoc子程序,转换成字符串后写入目标内存段。此时在data段中存储的都是字符串形式的,并且以0为结尾。在此设计中,我们一律使用修改后的32位转换子程序ddtoc

       4)由于对应 “收入-income”的数据,采用的是dd类型,在dtoc子程序中,除法指令div运算的结果ax已经放不下了。需要我们调用另一个divdw子程序来支持。这样就需要修改下dtoc子程序,并编写成的32位转换子程序ddtoc。并重新看看divdw程序是否满足要求。

       5)程序的大体框架是:将实验七的程序作为一个子程序,例如:to_table;主程序负责将table段的数据写入data段中(其中读取的数值需调用ddtoc将数字转换成字符串),最后调用show_str来显示字符串。

编程实现:

       一)首先将实验七的程序改写下,变成一个子程序,方便主程序的调用。to_table

代码如下:

assume cs:code 

data1 segment  

    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'  

    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'  

    db '1993','1994','1995' 

    ;以上是表示21年的21个字符串

    dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514  

    dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000  

    ;以上是表示21年公司总收入的21dword型数据

    dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226  

    dw 11542,14430,15257,17800

    ;以上是表示21公司雇员人数的21Word型数据。

data1 ends  

table segment  

    db 21 dup ('year summ ne ?? ')  

table ends

code segment

start: 

        ;初始化2个数据段,并将dsbx指向data1essi指向table

        mov ax,data1

        mov ds,ax

        mov ax,table

        mov es,ax

        ;将数据创建生成table

        call to_table           ;调用子程序

       

        mov ax, 4c00H

        int 21H

;------

;to_table:

;功能:将data1中的数据整理并写入table段中

;入口参数:data1内存段、table内存段

;返回值:无

;------    

to_table:

        ;保护寄存器变量

        push ax

        push ds

        push es

        push bx

        push si

        push di

        push cx

        ;to_table中需要使用的寄存器变量都保存起来。

       

        ;初始化偏址变量

        mov bx,0

mov si,0

        mov di,0

       

        mov cx,21               ;初始化计数器

    s66:   

        ;写入年份

        mov ax,0[bx]

        mov es:0[si],ax

        mov ax,2[bx]

        mov es:2[si],ax

        ;写入空格

        mov al,20H

        mov es:4[si],al

        ;写入收入

        mov ax,84[bx]

        mov es:5[si],ax

        mov ax,86[bx]

        mov es:7[si],ax

        ;写入空格

        mov al,20H

        mov es:9[si],al

        ;雇员数

        mov ax,168[di]

        mov es:10[si],ax

        ;写入空格

        mov al,20H

        mov es:12[si],al

        ;除法后写入人均收入

        mov ax,[bx+84]

        mov dx,[bx+86]

        ;没有办法,用个bp变量吧

        mov bp,[di+168]

        div bp

        mov es:13[si],ax

        ;写入空格

        mov al,20H

        mov es:15[si],al

        ;bxsidi变量的递增

        add bx,4

        add si,16

        add di,2

        loop s66

        ;恢复寄存器变量,并返回主调程序

        pop cx

        pop di

        pop si

        pop bx

        pop es

        pop ds

        pop ax

        ret

;-----

code ends

end start

       程序讲解:

       1)这个子程序在主程序中,直接调用就可以了。这里我们要对子程序有个初步的认识:子程序相当于其他用户和程序员来说,就是个黑盒子;你不必去关心它的内部运行如何?你只关心它需要什么样子的数据(参数、入口参数)。它的运行结果是什么?返回值是什么?

       子程序运行后,达到什么目的(是进行了复杂运算了等等),给调用的主程序的返回值(就是主程序需要的一些结果值)。

       2)我们来具体分析下这个子程序:

       子程序名称:to_table

       功能:将data1中的数据按要求整理,并写入table内存段中

       入口参数:data1内存段、table内存段。

       返回值:无

       3)这个子程序的改造,我们其实什么也不需要改动,只是将它的头加上子程序名称to_table的标号,前部是变量的保存(压栈);后部变量的恢复(弹栈),结尾加上ret

       4)此时需要注意子程序中的标号的使用:尽量选用有意义的标号名(虽然机器不认这东东,但编译器识别它);在一个程序中(有许多子程序),标号应该不重名,否则编译器会报错!在上面程序中,看看S66标号。最好使用英语及组合名(哎,实在英语不强的,汉语拼音行不?这时候你需要一个软件:词霸)。

       5)我们把这个编译、连接后,debug看看它是否能运行。这个运行没有错误。

       6)注意程序运行后,将公司的数据(data1内存段)和table段内存(已经整理好的数据)都写入到了相应的内存中去了。

      二)由于要用到divdw这个子程序,我们稍微改动下,因为调用它的程序有个寄存器变量有冲突。

汇编代码如下:(测试状态)

assume cs:code

code segment

start:    ;实现47F4240H/7B4H

          ;入口参数赋值

      mov ax, 4240H

      mov dx, 47FH

      mov cx, 7B4H

     

      call divdw

     

      mov ax, 4c00H

      int 21H

;-------

;子程序名称:divdw

;功能:实现32位的除法,被除数存储在216位的寄存器中,除数存储在116    ;寄存器中;余数存储在另一个16位寄存器中。解决div除法溢出的问题。

;入口参数:被除数的(ax)低16位,(dx)高16位,除数:(cx)。

;返回值:结果的商:(ax)低16位,(dx)高16位,余数:(bp)。

;-----------

divdw:                        ;子程序开始

      push ax             ;将被除数低16位先压栈保存。

      mov ax, dx          ;ax=dx

      mov dx, 0000H       ;

      div cx              ;被除数dx+ax(组合),除数cx

      mov bx, ax          ;H/N结果的商先保存在bx中,(bx=0001H

     

      pop ax              ;L值弹栈到ax

      div cx              ;此时(dx=0005H,(ax=4240H,组合成54240H

      mov bp, dx          ;返回值(cx)等于最终结果的余数

      mov dx, bx          ;最终结果高16位值=bx

      ret

;------------    

code ends

end start

       程序测试:单独编译并连接后,debug结果如下:

AX=9574  BX=0000  CX=07B4  DX=0000  SP=0000  BP=00B0  SI=0000  DI=0000

DS=0B55  ES=0B55  SS=0B65  CS=0B65  IP=000C   NV UP EI PL NZ NA PO NC

0B65:000C B8004C        MOV     AX,4C00

32位除法:47F4240H/7B4H等价于75448896/1972

运算的结果是商是:00009574H38260),余数00B0H176);其中结果的商低16位存储在ax中,高16位在dx中,余数在bp中。它们就是返回值。

       三)改写dtocddtoc子程序。(都叫ddtoc,我也叫这个吧!)

程序分析:

       1)程序的目的:是将一个存储在dx(高16位)+ax(低16位的)的数值组合后的十进制数值,以字符串形式,并末尾为0;按照顺序写入到data内存段中。

       2)这个子程序也适用于16位的字符转换,当然,你把16位存储ax中,(dx=0000H

       3)它的原理也是将这个32位的数值除以10,求它的余数;王爽老师希望我们使用jcxz指令用于判断商是否为0?在代码中,怎样判断商为0?如果高16位和低16位的值相加为0,那么这个数肯定是0

       4)在程序运行过程中,要注意保存和及时恢复axdx的值,因为它们是divdw的入口参数。

       5)注意栈空间的使用,因为程序就使用了一个栈空间结构(自动的,免费的。呵呵!),它的栈帧值在头脑中要有个清楚的认识,先进后出,后进先出,栈帧都是16位的空间单元。

       6)继续熟悉调用子程序时,入口参数的意义;返回值;程序的目的。

程序代码如下:并测试

assume cs:code 

data segment

    db 10 dup (0)

data ends

 

code segment

start:  ;程序测试:47F4240H==75448896,只是测试

        ;dssi指向data

        mov ax, data

        mov ds, ax

        mov si, 0

        ;入口参数赋值

        mov ax, 4240H

        mov dx, 047FH

        call ddtoc          ;调用子程序

   

        mov ax, 4c00H

        int 21H

;-----

;子程序名称:ddtoc

;功能:将一个32位数字转换成字符串,并写入data段中。

;入口参数:ax(16位), dx(高16位)

;返回值:无

;-----     

ddtoc:      ;保护寄存器变量值,因为下面的变量子程序都用到。

            push ax

            push cx

            push bx

            push si

            push bp

            push dx

           

            mov si, 0       ;偏移地址置零

change: mov cx, 10      ;设置除数cx=10

            mov bx, 0       ;divdw中导致bx变化,故清零

            mov bp, 0       ;余数bp=0

            call divdw      ;将(dx+ax/cx求余数bp     

           

            push ax         ;axdx压栈保护

            push dx

           

            add ax, dx      ;dx+ax)整个的商的值

            mov cx, ax      ;将商赋值给cx,判断整个的商是否为0   

               

            pop dx          ;axdx弹栈恢复

            pop ax

 

            jcxz last       ;判断cx是否为0

            add bp, 30H     ;将数字转换成ASCII

            push bp         ;将字符的ASCII值压栈保存

           

            inc si

            jmp short change

   

    last:   ;最后一次也要转换并压栈

            add bp, 30H     ;将数字转换成ASCII

            push bp         ; 将字符的ASCII值压栈保存

            inc si         

            ;将栈中数据倒序写入内存data段中   

            mov cx, si      ;si=数字的字符个数,设置循环次数

            mov si, 0

    write:  pop ds:[si]

            inc si

            loop write

           

            mov byte ptr ds:[si], 0 ;0作为字符串结尾。

    ;恢复寄存器,并返回主调程序。

            pop dx

            pop bp

            pop si

            pop bx

            pop cx

            pop ax

            ret

;-----------   

;子程序名称:divdw

;功能:实现32位的除法,被除数存储在216位的寄存器中,除数存储在116  ;寄存器中;余数存储在另一个16位寄存器中。解决div除法溢出的问题。

;入口参数:被除数的(ax)低16位,(dx)高16位,除数:(cx)。

;返回值:结果的商:(ax)低16位,(dx)高16位,余数:(bp)。

;-----------

divdw:                      ;子程序开始

        push ax             ;将被除数低16位先压栈保存。

        mov ax, dx          ;ax=dx

        mov dx, 0000H       ;

        div cx              ;被除数dx+ax(组合),除数cx

        mov bx, ax          ;H/N结果的商先保存在bx中,(bx=0001H

       

        pop ax              ;L值弹栈到ax

        div cx              ;此时(dx=0005H,(ax=4240H,组合成54240H

        mov bp, dx          ;返回值(cx)等于最终结果的余数

        mov dx, bx          ;最终结果高16位值=bx

        ret;------------       

code ends

end start

       程序结果:
       将程序编译,连接后,使用debug测试:
-d ds:0
0B65:0000  37 35 34 34 38 38 39 36-00 00 00 00 00 00 00 00   75448896
........
       由于47F4240H==75448896,故结果完全正确!
       程序分析:
       1ddtoc子程序,包含了一个divdw子程序的调用,前面我们已经将divdw子程序也调试完毕了,并且在ddtoc子程序中调用正常了。我们就不用关注divdw了,目前我们只关心ddtoc这个子程序就可以了。
       2ddtoc子程序的入口参数是:ax(16位), dx(高16位)。它们的组合是一个32位的数值,换句话说,将一个数存储在dx+ax中,调用ddtoc就能将这个数转换成字符串,并且存储在data内存段中。
    3)注意在汇编语言中字符串的存储方法,最后一个字节写入0(数值)
    讲解:这里虽然data段内存都是0(数值),但为了保障我们每次向data段写入的是一个字符串,必须在字符序列最后写入一个0作为结尾。
       四)考察show_str子程序:
子程序功能:将dssi指向的内存段中的字符串写入到显存中,并设置相应的字符显示属性。
入口参数:dh-屏幕输出开始的行数;dl-屏幕输出开始所在的列数;cl-颜色及字符属性。
返回值:无
       这个子程序目前没有什么问题,就是将字符串显示在屏幕上。
 
       五)编写清屏程序:cls
       要满足课程设计1的要求(根据书中的显示结果),我们发现dos或命令提示符窗口没有乱七八糟的显示字符,这提示我们程序首先运用了清屏的功能。这个功能将来我们可能要重复使用,故将其编写成一个子程序。
;----------
;程序名称:cls
;程序功能:dos或命令提示符窗口清屏,满黑显示
;入口参数:无
;返回值:无
;----------
cls:        push cx
            push di
            push si             ;将子程序用到的寄存器变量入栈保存
           
            mov ax, 0b800H     
            mov es, ax          ;设置esdi指向显示缓冲区内存段
           
            mov cx, 80*24       ;设置循环次数,屏幕是2480列,这注意
            mov di, 0
           
scr_cls:    mov byte ptr es:[di+0], ' ' ;第一个字节写入空格,
            mov byte ptr es:[di+1], 0   ;第二个字节写入字符属性0(代表黑色无底)
            inc di                     
            inc di                      ;为毛这样?自己考虑(11章有提示)
            loop scr_cls
            ;恢复寄存器
            pop si             
            pop di
            pop cx             
            ret
        六)编写主程序,并调用子程序。
        我们开始实施方案,大体程序的框架是:
    数据段开始
    ……
    数据段结束
    code segment
    main:
        ;调用子程序to_table生成table数据段,包括入口参数初始化
        call to_table  
        ;调用cls子程序,清屏
        call cls
        ;将table段中内容,按照显示格式写入到data
        ……(其中使用ddtocshow_strdivdw子程序。)
        ;调用show_str子程序将data段字符串显示。
        call show_str
 
        mov 4c00H
        int 21H
    ;----
    to_table:
        
        ret
    ;----
    cls:
        
        ret
    ;----
    show_str:
        
        ret
    ;----
    ddtoc:
        
        ret
    ;----
    divdw:
        
        ret
    code ends
    end main
       其他的子程序都准备好了。我们开始编写最后一个主程序:

程序代码如下:

assume cs:code 
data1 segment  
    db '1975','1976','1977','1978','1979','1980','1981','1982','1983'  
    db '1984','1985','1986','1987','1988','1989','1990','1991','1992'  
    db '1993','1994','1995' 
    ;以上是表示21年的21个字符串
    dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514  
    dd 45980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000  
    ;以上是表示21年 公司总收入的21dword型数据
    dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226  
    dw 11542,14430,15257,17800
    ;以上是表示21公司雇员人数的21Word型数据。
data1 ends  
table segment  
    db 21 dup ('year summ ne ?? ')  
table ends
data segment
    db 10 dup (0)
data ends
code segment
start: 
        ;初始化2个数据段,并将ds指向data1es指向table
        mov ax,data1
        mov ds,ax
        mov ax,table
        mov es,ax
        ;将数据创建生成table
        call to_table           ;调用子程序
       
        call cls                ;清屏
       
        ;初始化2个数据段,es:bx继续指向tableds:si指向data段。
        mov ax, table
        mov es, ax
        mov bx, 0
        mov ax, data
        mov ds, ax
        mov si, 0
       
         mov cx, 21          ;一共显示21行,初始化cx计数器

     mov dh, 1           ;初始在屏幕第几行显示,show_str的入口参数。

show_info: 

        push cx             ;计数器压栈,保存cx,下面经常使用cx,保护

       

        ;显示年份

        mov ax, es:[bx]    

        mov ds:[si], ax    

        mov ax, es:[bx+2]   ;注意[bx+idata]的寻址方式

        mov ds:[si+2], ax   ;由于年份就是字符串,直接复制到data段中。    

        mov byte ptr ds:[si+4], 0   ;0作为字符串结尾。

       

        mov dl, 3           ;设置入口参数:所在行的列数,从第几列开始显示

        mov ch, 0           ;ch清零,防止高8位不为零。

        mov cl, 2           ;字符颜色属性(此处应是二进制数0000 0010代表绿色)

        call show_str       ;调用子程序,显示字符串。

               

        ;显示总收入     

        push dx             ;后面要使用dx变量,先保存dx

        mov ax, es:[bx+5]   ;将总收入的低16位送入ax

        mov dx, es:[bx+7]   ;将总收入的高16位送入dxddtoc的入口参数

        call ddtoc          ;将总收入转换成字符串后写入data段中。

        pop dx              ;恢复寄存器dx,此时dh也就恢复了。

       

        mov dl, 14          ;所在行的列数

        mov ch, 0           ;ch清零,防止高8位不为零。

        mov cl, 2           ;字符颜色属性(此处应是二进制数0000 0010

        call show_str

       

        ;显示公司总人数

        push dx

        mov ax, es:[bx+10]

        mov dx, 0

        call ddtoc

        pop dx

       

        mov dl, 34          ;所在行的列数

        mov ch, 0           ;ch清零,防止高8位不为零。

        mov cl, 2           ;字符颜色属性(此处应是二进制数0000 0010

        call show_str

       

        ;显示总收入

        push dx

        mov ax, es:[bx+13]

        mov dx, 0

        call ddtoc

        pop dx

       

        mov dl, 50          ;所在行的列数

        mov ch, 0           ;ch清零,防止高8位不为零。

        mov cl, 2           ;颜色属性(此处应是二进制数0000 0010

        call show_str

                       

        add bx, 10H         ;bx指向下一行。(table16字节是一行)

        mov si, 0           ;si置零,dssi总指向第一个字节。

        add dh, 1           ;累加dh(下一行显示)

       

        pop cx              ;弹栈到cx,计数器自动减1.

        loop show_info      ;循环,直到cx=0

       

        mov ax, 4c00H

        int 21H

;------

;to_table:

;功能:将data1中的数据整理并写入table段中

;入口参数:data1内存段、table内存段

;返回值:无

;------    

to_table:

        ;保护寄存器变量

        push ax

        push ds

        push es

        push bx

        push si

        push di

        push cx

        ;to_table中需要使用的寄存器变量都保存起来。

       

        ;初始化偏址变量

        mov bx,0

        mov si,0

        mov di,0

       

        mov cx,21               ;初始化计数器

    s66:   

        ;写入年份

        mov ax,0[bx]

        mov es:0[si],ax

        mov ax,2[bx]

        mov es:2[si],ax

        ;写入空格

        mov al,20H

        mov es:4[si],al

        ;写入收入

        mov ax,84[bx]

        mov es:5[si],ax

        mov ax,86[bx]

        mov es:7[si],ax

        ;写入空格

        mov al,20H

        mov es:9[si],al

        ;雇员数

        mov ax,168[di]

        mov es:10[si],ax

        ;写入空格

        mov al,20H

        mov es:12[si],al

        ;除法后写入人均收入

        mov ax,[bx+84]

        mov dx,[bx+86]

        ;没有办法,用个bp变量吧

        mov bp,[di+168]

        div bp

        mov es:13[si],ax

        ;写入空格

        mov al,20H

        mov es:15[si],al

        ;bxsidi变量的递增

        add bx,4

        add si,16

        add di,2

        loop s66

        ;恢复寄存器变量,并返回主调程序

        pop cx

        pop di

        pop si

        pop bx

        pop es

        pop ds

        pop ax

        ret

;-----

;子程序名称:ddtoc

;功能:将一个32位数字转换成字符串,并写入data段中。

;入口参数:ax(16位), dx(高16位)

;返回值:无

;-----     

ddtoc:      ;保护寄存器变量值,因为下面的变量子程序都用到。

            push ax

            push cx

            push bx

            push si

            push bp

            push dx

           

            mov si, 0       ;偏移地址置零

           

    change: mov cx, 10      ;设置除数cx=10

            mov bx, 0       ;divdw中导致bx变化,故清零

            mov bp, 0       ;余数bp=0

            call divdw      ;将(dx+ax/cx求余数bp    

           

            push ax         ;axdx压栈保护

            push dx

           

            add ax, dx      ;dx+ax)整个的商的值  

            mov cx, ax      ;将商赋值给cx,判断整个的商是否为0

               

            pop dx          ;axdx弹栈恢复

            pop ax

 

            jcxz last       ;判断cx是否为0

            add bp, 30H     ;将数字转换成ASCII

            push bp         ;将字符的ASCII值压栈保存

           

            inc si

            jmp short change

   

    last:   ;最后一次也要转换并压栈

            add bp, 30H     ;将数字转换成ASCII

            push bp         ; 将字符的ASCII值压栈保存

            inc si         

    ;将栈中数据倒序写入内存data段中 

            mov cx, si      ;si=数字的字符个数,设置循环次数

            mov si, 0

    write:  pop ds:[si]

            inc si

            loop write

           

            mov byte ptr ds:[si], 0 ;0作为字符串结尾。

    ;恢复寄存器,并返回主调程序。

            pop dx

            pop bp

            pop si

            pop bx

            pop cx

            pop ax

            ret

;-------

;子程序名称:divdw

;功能:实现32位的除法,被除数存储在216位的寄存器中,除数存储在116;寄存器中;余数存储在另一个16位寄存器中。解决div除法溢出的问题。

;入口参数:被除数的(ax)低16位,(dx)高16位,除数:(cx)。

;返回值:结果的商:(ax)低16位,(dx)高16位,余数:(bp)。

;-----------

divdw:                      ;子程序开始

        push ax             ;将被除数低16位先压栈保存。

        mov ax, dx          ;ax=dx

        mov dx, 0000H       ;

        div cx              ;被除数dx+ax(组合),除数cx

        mov bx, ax          ;H/N结果的商先保存在bx中,(bx=0001H

       

        pop ax              ;L值弹栈到ax

        div cx              ;此时(dx=0005H,(ax=4240H,组合成54240H

        mov bp, dx          ;返回值(cx)等于最终结果的余数

        mov dx, bx          ;最终结果高16位值=bx

        ret

;---------

;程序名称:cls

;程序功能:dos或命令提示符窗口清屏,满黑显示

;入口参数:无

;返回值:无

;----------

cls:        push cx

            push di

            push si             ;将子程序用到的寄存器变量入栈保存

           

            mov ax, 0b800H     

            mov es, ax          ;设置esdi指向显示缓冲区内存段

           

            mov cx, 80*24       ;设置循环次数,屏幕是2480

            mov di, 0

           

scr_cls:    mov byte ptr es:[di+0], ' ' ;第一个字节写入空格,

            mov byte ptr es:[di+1], 0   ;第二个字节写入字符属性0(代表黑色无底)

            inc di                     

            inc di                      ;为毛这样?自己考虑(11章有提示)

            loop scr_cls

            ;恢复寄存器

            pop si             

            pop di

            pop cx             

            ret

;------------  

;show_str功能 :按行和列及字符属性显示字符串  

    ;入口参数:dh-行数、dl-列数、cl-字符属性, ds:si

    ;返回值:无

show_str:   push dx

            push cx

            push si            

            push bx            

            push es             ;将子程序用到的寄存器入栈

           

            mov ax, 0b800H

            mov es, ax          ;设置显示缓冲区内存段

           

            mov ax, 0           ;ax= 0,防止高位不为零  

            mov al, 160         ;0a0H-   160字节/

            mul dh              ;相对于0b800:0000dh行偏移量

            mov bx, ax          ;将第(dh)行的偏移地址送入bxbx代表行偏移

            mov ax, 0

            mov al, 2           ;列的标准偏移量是2个字节

            mul dl              ;同一行列的偏移量,尽量使用乘法,(al=列偏移

            add bx, ax          ;最终获得偏移地址(bx)=506H

            mov di,0            ;di作为每个字符的偏移量

            mov al, cl          ;将字符属性写入al

            mov ch, 0           ;cx8位设置为0

           

    show:   mov cl, ds:[si]     ;将字符串单个字符读入cl

            jcxz ok             ;判断字符串是否为零。

            mov es:[bx+di+0], cl    ;在显示缓冲区中写入字符

            mov es:[bx+di+1], al    ;在显示缓冲区中写入字符属性

            add di, 2

            inc si

            jmp short show

   

        ok: ;字符串字符为0,结尾

            pop es              ;恢复寄存器

            pop bx

            pop si             

            pop cx

            pop dx             

            ret

code ends

end start

最终结果是:

   1975       16                               5

   1976       22                               3

   1977       382                              42

   1978       1356                13              104

   1979       2390                28              85

   1980       8000                38              210

   1981       16000               130             123

   1982       24486               220             111

   1983       50065               476             105

   1984       97479               778             125

   1985       140417              1001            140

   1986       197514              1442            136

   1987       345980              2258            153

   1988       590827              2793            211

   1989       803530              4037            199

   1990       1183000             5635            209

   1991       1843000             8226            224

   1992       2759000             11542           239

   1993       3753000             14430           260

   1994       4649000             15257           304

   1995       5937000             17800           333

       程序总结:

       1)在子程序中,如果有破坏(修改)寄存器变量的行为,我们要及时保护寄存器变量的值,并及时恢复。

       2)加深栈的理解:pushpop是成对的,要避免pushpop操作的不是一个字单元,这样会导致程序错误。

       3)理解子程序的黑盒子理论。考虑入口参数、功能、返回值。

 

       4)内存的寻址方式。

 

 

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有