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

课程设计1

 

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

       讲解:噢!在搞了几个小程序后,终于开始接触一个规模比较大的程序了。这个任务也是建立在这些小的程序基础上的。

实验目的:

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

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

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

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

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

编程分析:

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

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

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

       当然如果你接触了数据库系统后,你发现这都不是个事!

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

       3)table段数据的读取并写入一个临时的内存存储段中。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年 公司总收入的21个dword型数据

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

    dw 11542,14430,15257,17800

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

data1 ends  

table segment  

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

table ends

code segment

start: 

        ;初始化2个数据段,并将ds:bx指向data1,es:si指向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

        ;bx、si、di变量的递增

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

       参数、返回值在哪里存储?一般在寄存器中,或在栈中。

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

       C语言随想:与C中的函数或子程序一样,都有一些形参(在子程序内部的寄存器变量),实参(调用的主程序中的一些寄存器变量)。返回值:在C中是通过变量返回给主调程序(在汇编中依然是寄存器的变量是主力。)

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

       子程序名称:to_table

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

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

       讲解:这里需要在主程序中提供的数据:也就是二个数据段了。其他的参数什么也不需要。

       返回值:无

       讲解:这里并不需要什么返回值,调用这个子程序将其功能实现了就可以了。

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

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

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

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

       7)再次强调下,在调用子程序过程中,凡是涉及到破坏(或改变)寄存器变量的行为,我们都应该在子程序中通过栈进行保护。

       这个子程序经过编译连接后,我们使用debug验证下它是否正确。

       二)由于要用到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位的除法,被除数存储在2个16位的寄存器中,除数存储在1个16位    ;寄存器中;余数存储在另一个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;

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

       子程序测试结果:没有问题。此时注意,调用这个子程序时,需要3个入口参数。

       三)改写dtoc为ddtoc子程序。(都叫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)在程序运行过程中,要注意保存和及时恢复ax和dx的值,因为它们是divdw的入口参数。

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

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

程序代码如下:并测试

assume cs:code 

data segment

    db 10 dup (0)

data ends

 

code segment

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

        ;ds:si指向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         ;将ax和dx压栈保护

            push dx

           

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

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

               

            pop dx          ;将ax和dx弹栈恢复

            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位的除法,被除数存储在2个16位的寄存器中,除数存储在1个16位  ;寄存器中;余数存储在另一个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,故结果完全正确!

       程序分析:

       1)ddtoc子程序,包含了一个divdw子程序的调用,前面我们已经将divdw子程序也调试完毕了,并且在ddtoc子程序中调用正常了。我们就不用关注divdw了,目前我们只关心ddtoc这个子程序就可以了。

       2)ddtoc子程序的入口参数是:ax(低16位), dx(高16位)。它们的组合是一个32位的数值,换句话说,将一个数存储在dx+ax中,调用ddtoc就能将这个数转换成字符串,并且存储在data内存段中。

    3)注意在汇编语言中字符串的存储方法,最后一个字节写入0(数值)

    讲解:这里虽然data段内存都是0(数值),但为了保障我们每次向data段写入的是一个字符串,必须在字符序列最后写入一个0作为结尾。

       四)考察show_str子程序:

子程序功能:将ds:si指向的内存段中的字符串写入到显存中,并设置相应的字符显示属性。

入口参数:dh-屏幕输出开始的行数;dl-屏幕输出开始所在的列数;cl-颜色及字符属性。

返回值:无

       这个子程序目前没有什么问题,就是将字符串显示在屏幕上。

 

       五)编写清屏程序:cls

       要满足课程设计1的要求(根据书中的显示结果),我们发现dos或命令提示符窗口没有乱七八糟的显示字符,这提示我们程序首先运用了清屏的功能。这个功能将来我们可能要重复使用,故将其编写成一个子程序。

;----------

;程序名称:cls

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

;入口参数:无

;返回值:无

;----------

cls:        push cx

            push di

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

           

            mov ax, 0b800H     

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

           

            mov cx, 80*24       ;设置循环次数,屏幕是24行80列,这注意

            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

       讲解:我们发现80*24编译器可以理解。呵呵。还有就是我们在debug中,内存的显示形式是16个字节一行(也就是10H),其实它就是一个线性的空间,一维的,为了我们好理解,才显示成二维或三维形式。

       六)编写主程序,并调用子程序。

       所有的关于课程设计1的问题都妥善解决了,下面我们要考虑怎样显示在屏幕上了。

直接在主程序中写代码显示;

       我们开始实施方案,大体程序的框架是:

    数据段开始

    ……

    数据段结束

    code segment

    main:

        ;调用子程序to_table生成table数据段,包括入口参数初始化

        call to_table  

        ;调用cls子程序,清屏

        call cls

        ;将table段中内容,按照显示格式写入到data中

        ……(其中使用ddtoc和show_str、divdw子程序。)

        ;调用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

       其他的子程序都准备好了。我们开始编写最后一个主程序:

程序分析:

       1)我们从table段内存中发现,第一个数据是字符串,其他的都是数字存储的。

       2)屏幕显示:按照我们理解,每一行

       第一列数据(年份):数据前面有2个空格,年份是4个字符显示,共需要6个字符的占位。

       实现过程:直接将其复制到data段中,并结尾加0;调用show_str显示

       第二列(总收入):前面有8个空格(大约吧,其实我们没数),然后是总收入从左到右显示(需要8个字符占位),共需要16个字符的占位。

       实现过程:将数据共32位读出后,调用ddtoc,将其转换成字符串,写入data段中,并结尾加0;调用show_str显示

       第三列(公司总人数):前面有XXXX(XXXX代表我们没数,看源程序)个空格,(需要6个字符占位),共需要XXXX个字符占位。

       实现过程:将数据共16位,读出,调用ddtoc,将其转换成字符串,写入data段中,并结尾加0;调用show_str显示。这里注意。我都调用了32位的转换字符串的子程序。

       第四列(平均收入):前面有XXXX个空格,(需要4个字符占位),共需要12个字符占位。

       具体的每列的显示,在主程序中可以调节。

程序代码如下:

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年 公司总收入的21个dword型数据

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

    dw 11542,14430,15257,17800

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

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指向data1,es指向table

        mov ax,data1

        mov ds,ax

        mov ax,table

        mov es,ax

        ;将数据创建生成table段

        call to_table           ;调用子程序

       

        call cls                ;清屏

       

        ;初始化2个数据段,es:bx继续指向table,ds: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位送入dx,ddtoc的入口参数

        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指向下一行。(table中16字节是一行)

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

        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

        ;bx、si、di变量的递增

        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         ;将ax和dx压栈保护

            push dx

           

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

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

               

            pop dx          ;将ax和dx弹栈恢复

            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位的除法,被除数存储在2个16位的寄存器中,除数存储在1个16位 ;寄存器中;余数存储在另一个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          ;设置es:di指向显示缓冲区内存段

           

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

            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:0000第dh行偏移量

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

            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           ;将cx高8位设置为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                  3               5

   1976       22                  7               3

   1977       382                 9               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)加深栈的理解:push和pop是成对的,要避免push和pop操作的不是一个字单元,这样会导致程序错误。

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

       4)内存的寻址方式。

posted @ 2017-05-21 09:13  筑基2017  阅读(1858)  评论(1)    收藏  举报