王爽《汇编语言》第三版 课程设计1

先上代码:

assume cs:codesg
;将整个data段看作是一个数组,长度一共为
;21*4+21*4+2*21=168+42=210字节
data 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'
    ;长度为84的数组,一个字符一个字节:4X21

    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

;ax+210/16向上取整,即ax+14h
;对于ds,偏移量为16*14=224
table segment
    db 21 dup('year summ ne ?? ')
table ends

show segment
    db 100 dup(0)
show ends

temp segment
    dw 10 dup(0)
temp ends

codesg segment
start:      mov ax, data
            mov ds, ax 
            mov bx, table
            mov es, bx  
            mov bx, 0   ;定位结构体数组元素
            mov di, 0   ;定位data中的4字节数据
            mov si, 0   ;定位data中的2字节数据
            mov cx, 15h
s0:         ;转移年份
            mov ax, ds:[di];
            mov es:[bx], ax
            mov ax, ds:[di+2]
            mov es:[bx+2], ax
            ;添加空格 
            mov byte ptr es:[bx+4], 32

            ;转移收入
            ;作为被除数
            mov ax, ds:[di+84];
            mov es:[bx+5], ax
            mov ax, ds:[di+86]
            mov es:[bx+7], ax
            ;添加空格 
            mov byte ptr es:[bx+9], 32

            ;转移雇员数量
            mov ax, ds:[si+168]
            mov es:[bx+10], ax
            ;添加空格 
            mov byte ptr es:[bx+12], 32

            ;计算人均收入,并转移到table中
            mov ax, es:[bx+5]   ;低16位
            mov dx, es:[bx+7]   ;高16位
            div word ptr es:[bx+10]
            mov es:[bx+13], ax  ;商默认存放在ax中 
            ;添加空格 
            mov byte ptr es:[bx+15], 32
            ;操作完成,bx加16,di加2
            add bx, 16  
            add di, 4
            add si, 2
            loop s0


;s0循环的作用主要是将数据存储到table段中
;而在s1循环中,先把table段中的数据转化为字符串,转储到
;show段中,然后再在s2循环中调用show_str子程序来将这些字符串一行一行地
;显示到屏幕上
            mov ax, table
            mov ds, ax
            mov ax, show
            mov es, ax
            mov dx, 0
            mov dh, 4
            mov bx, 0
            mov cx, 15h
s1:         mov di, 0
            ;这里的di作为table数据段的指针
            push ds
            push es
            push cx
            push dx
            ;保存行列信息,在调用show_str时会用到
            ;转储年份
            mov ax, ds:[bx+di]
            mov es:[di], ax
            add di, 2
            mov ax, ds:[bx+di]
            mov es:[di], ax
            mov byte ptr es:[di+4], 0
            ;转储收入
            mov di, 5
            mov ax, ds:[bx+di]
            mov dx, ds:[bx+di+2]
            call dtoc 
            ;转储人数
            mov ax, ds:[bx+10]
            mov dx, 0
            call dtoc 
            ;转储人均收入
            mov ax, ds:[bx+13]
            mov dx, 0
            call dtoc
            ;以上就是table段中的一行转换为ASCII码值的处理过程

            ;下面开始显示过程,此过程需要循环四次
            pop dx          
            mov di, 0
            ;这里的di作为待输出数据段的指针
            mov cx, 4
s2:         mov si, 0
            call show_str
            add dl, 20
            ;把列错开
            loop s2
            mov dl, 0
            add dh, 1
            add bx, 16
            pop cx
            pop es
            pop ds
            loop s1

            mov ax, 4c00h
            int 21h

dtoc:       ;该子程序用于将数值型的数字转换为字符串
            ;十进制数值转换为ASCII码值,转换关系为:ascii=10进制+30H
            ;要想将一个十进制的整数拆分成一个一个的数值,那我们需要让这个数
            ;除以10,然后将得到的结果依次入栈,除完之后再依次出栈,即可得到由高位到低位
            ;的所有数值,之后将这些值加上30H,即得到其对应的ASCII码值,然后将这些
            ;ASCII码值存放到一个数据段中,调用show_str函数,来在屏幕上显示这些数值
            ;为了存储转换后的ASCII码值,我们需要新开辟一个数据段
            push bx
            push cx
            push dx
            push ds 
            push ax
            push si
            push es

            mov bx, 0
            ;记录十进制数据的位数
split:      mov cx, 0ah
            ;cx存放除数  
            call divdw

            ;如果被除数大于2550,al是无法存放商的,会造成溢出,因此,我们需要调用本实验中第二个程序
            ;专门用于解决除法溢出问题的程序
            ;此程序返回运算后的商和余数,分别保存在ax和cx中

            push cx
            ;余数入栈
            inc bx
            ;当循环终止的时候可以进行弹栈存储操作了,但是我们需要一个标记,来标识我们需要
            ;弹出多少次,我们使用bx来进行存储
            mov cx, ax
            add cx, dx
            ;ax中的值在下一次运算中一定会用到,dx中的值也有可能会用到(当被除数很大时)
            ;此时可以临时保存数据的只有cx了,因此我们直接将运算结果放到cx中
            ;一举两得
            ;判断条件是商==0,因此我么需要高16位和低16位全都为0
            jcxz ok1
            ;处理过程是需要循环的,循环结束的条件是商==0 
            ;我们只需要将执行jcxz指令即可,当cx的值位0的时候,它会自动跳转到ok1循环的
            jmp short split 
ok1:        pop ax
            add al, 30h
            mov byte ptr es:[di], al
            inc di
            dec bx
            mov cx, bx
            jcxz last
            jmp short ok1

last:       ;最后一步,在数据的ASCII数据形式的最后加上一个0
            mov ah, 0
            mov byte ptr es:[di], ah 
            inc di

            pop es
            pop si
            pop ax 
            pop ds
            pop dx
            pop cx
            pop bx
            ret

divdw:      push ds
            push dx
            push cx
            push bx
            push ax 

            mov ax, temp
            mov ds, ax 
            mov ax, dx 
            mov dx, 0 
            div cx
            ;ax存放商,dx存放余数
            ;根据公式,使用被除数高位除以除数得到的商×65536
            ;*65536等价于在低位加160,因此操作就会变得非常简单
            ;使用被除数高位除以除数得到的余数×65536+被除数的低位,再将得到的结果除以除数
            ;两者的结果相加,即可得到32位/16位的无溢出结果
            push dx
            ;使用栈临时保存余数
            mov dx, ax
            mov ax, 0
            mov ds:[0], ax
            mov ds:[2], dx
            pop dx
            ;弹出余数,作为右操作数中被除数的高16pop ax
            ;得到被除数的低16push ax
            ;恢复栈顶数据,避免对主程序造成干扰 
            ;将右操作数[]中的左操作数的低16位和被除数的低16位相加
            ;但是右操作数[]中的左操作数的低16位一定是全0的,因此我们可以省略这一步
            ;直接执行pop ax,将被除数的低16位作为右操作数的低16位
            div cx
            ;ax存放商,dx存放余数
            ;由于左操作数的低16位一定是全0,所以不必与其相加,直接将
            ;右操作数的低16位存储到ds:[0]内存单元即可
            mov ds:[0], ax
            ;商的低16位放到ds:[0]单元中
            mov ds:[4], dx  
            ;余数放到ds:[4]单元中
            ;ds:[2]中一直保存的都是商的高16位,且没有被更改过,因此无须任何操作

            pop ax
            pop bx
            pop cx
            pop dx
            mov ax, ds:[0]
            ;ax保存商的低16位
            mov dx, ds:[2]
            ;dx保存商的高16位
            mov cx, ds:[4]
            ;cx保存余数  
            pop ds
            ;之所以要在pop ds之前将数据转移,是因为子程序divdw调用前,ds已经被使用
            ;指向的是其他的段,如果不在pop之前转移数据,那么div段的数据就无法获取了
            ret  

show_str:   push ax
            push bx
            push cx
            push dx
            push es 
            push ds
            ;根据上节中的框架,为了不让子程序干扰主程序中寄存器的值,将所有子程序会用到的寄存器进行压栈
            mov ax, 0b800h
            mov es, ax
            ;颜色区的段地址
            mov ax, show
            mov ds, ax
            ;要读入的数据区的段地址
            mov al, 160 
            mul dh
            ;每行占160个字节,乘以行数
            push ax
            ;将行计算的结果存储到栈中
            mov al, 2
            mul dl
            ;每列占2个字节,乘以列数
            pop bx
            ;将上次运算的结果(160×行数)的值转移到bx中
            add bx, ax  ;此时的ax值为(2×列数)
            ;将两者相加,最终结果保存到bx中
            mov dl, 130
            ;显示颜色为绿色
change:     mov cl, ds:[di]
            inc di 
            mov ch, 0
            jcxz ok
            mov ch, dl
            mov es:[bx+si], cx
            add si, 2 
            jmp short change

ok:         pop ds 
            pop es
            pop dx
            pop cx
            pop bx
            pop ax
            ret

codesg ends
end start

codesg ends
end start

codesg ends
end start

这可是我折腾了一天才调试好的代码 -_-|||
效果如下,我设置的是可闪烁的
这里写图片描述

其实也不是很难,就是程序太大了,所有的集训其都用上了,而且我么直接用的实验10里的三个试验中的代码,但是有很多需要改的地方,开始没发现,后来一点一点调试,才把所有的错误都揪出来,心好累

原来的解决除法溢出问题的代码需要改动几个地方才能在这里正确运行,应为需求不一样,所以在试验中可以省略的地方有可能在课程设计的代码中是不可以省略的,其实搞明白了也是挺简单的,考虑的时候严谨一点就好啦

猜你喜欢

转载自blog.csdn.net/include_heqile/article/details/80629599