汇编语言课程设计一
几周前,我写了一篇对数据进行结构化访问的文章
寻址方式在结构化数据访问中的应用
还有一篇是对子程序的撰写
编写子程序
在学习了许多各种各样的操作以后,我们今天就可以开始对之前这个实验美化一下了
太闪了,其实就是对实验7的数据输出到屏幕中去
在编写完这个程序之后,或许你会重新燃起对Assembly语言的热爱,我在其中出现了许多小插曲,下面就一一讲解一下思路以及过程
用到的数据段
assume cs:code, ds:data, ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984','1985'
db '1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514,345980
dd 590827,803530,1183000,1843000,2758000,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
number segment
db 1024 dup(0)
number ends
stack segment stack ;!!!事实证明,后面这个stack加上可以自动赋值sp,不加默认sp从0开始
db 1024 dup(0)
stack ends
这里,我现在才发现我一直都没有理解这段伪指令的含义,我之前的错误有二:
① 在assume伪指令那行,我之前都是拿空格分隔,导致一直都在报错,但不影响程序的执行(编译器还是有点智能的),实际上应加逗号分隔
② 直接assume数据段就完了?当然不是,为了代码的严谨性,我们在代码段里也应该对使用的数据段重新赋值
我上面代码中对栈的声明就是一个很好的例子,如果我只是使用stack segment声明,而栈顶标记sp就没有赋值,容易产生我进出栈的时候,致使sp为FFFE的情况
着重讲讲主要的子程序
延续的还是上一篇文章的子程序,只是我在其中发现了些逻辑上的错误!!!后面单开一篇来讲
div_dw(32位除法运算)
div_dw: ;(输入):ax低16位,dx高16位,cx为16位除数 ;(输出):dx结果高16位,ax结果低16位,cx为余数 push bx mov bx, ax ;暂存ax,低位 mov ax, dx ;设置被除数 mov dx, 0 ;高位置为0 div cx ;高16作除法,余数dx并到低16位,这里无需改动,dx也是除法的高位 push ax ;暂存高16位除法结果中的商 mov ax, bx div cx ;低16位除法,结果,ax为商,dx为余数 mov cx, dx pop dx ;将暂存的高位ax,置于dx pop bx ret
这个子程序给的参数很妙,不同于自带有的div指令,这个除法能完成所有的32位对16位的除法,只是精度不高
而自带有的div指令恰恰相反,这里也可以细细揣摩!dtoc(十进制数值转对应字符串)
dtoc: ;X输入ax,(16位) ;结果保存在number字段 ;注意,检测商为0才跳出!! push bx push cx push dx push ds push si mov bx, number mov ds, bx ;目标地址 mov si, 0 ;偏移指针,个数 mov bx, 10 ;除数 mov dx, 0 ;32位除法,但这里只有ax参与,dx得置0 in_num: div bx push dx inc si mov cx, ax ;这里没有loop循环,cx可放心使用 jcxz in_num_exit mov dx, 0 jmp in_num in_num_exit: mov cx, si mov si, 0 mov bx, 0 set_num: pop bx add bx, 30h mov ds:[si], bl inc si loop set_num mov byte ptr ds:[si], 0 ;末尾添0,表示字符串结束 pop si pop ds pop dx pop cx pop bx ret
这里得和之前版本区分一下,因为之前那个逻辑是错误的,对于栈的使用导致我写后面子程序的过程中处处碰壁
列举错误情况1
没有及时地将数据出栈,导致死循环,也可能会出现各种奇葩的问题,
这告诉我们编写代码的严谨性多么重要,你添一句,删一句,结果都可能出不来
对于这个子程序,唯一的缺点就是输出的数据只能是0~65535(无符号数),因此我对这个子程序改写了一下
dtoc_pro(升级版,转换的数值最高可达4294967295,也就是4个FFFF)
dtoc_pro: ;输入ax低16,dx高16 ;内容输出到number字段 ;注意,检测商为0才跳出 push bx push cx push si push ds mov si, 0 ;记录次数 mov bx, number mov ds, bx ;目标地址 in_num_pro: mov cx, 10 ;除数10 call div_dw ;返回余数cx push cx ;保存余数 inc si push cx mov cx, ax ;判断商为0 or cx, 0 or cx, dx jcxz in_num_pro_exit ;商为0,跳转,开始存入number字段 pop cx jmp in_num_pro in_num_pro_exit: pop bx ;!!!!!!!!!!!注意多余的push要及时出栈,影响数据排布!!!! mov cx, si mov si, 0 mov bx, 0 set_num_pro: pop bx ;写入目标地址操作 add bx, 30h mov ds:[si], bl inc si loop set_num_pro mov byte ptr ds:[si], 0 ;末尾添0,表示字符串结束 pop ds pop si pop cx pop bx ret
这里没有像普通版(dtoc)那样使用div指令,而是使用我们的指令div_dw,而输出的结果恰好也可以作为下次除法的被除数,可见这个大除法设计的多么巧妙
稍微注意一下,最后一个字节是添0,可以对比联想一下C语言中字符串的格式
这里讲讲上次子程序出现的错误之一:我们判断结束的标准应该是商为0,而不是余数为0。如果是使用后者,那么对于十进制数中穿插了0的数字(比如101)则只会输出个位的1
紧接着问题就来了,对于dtoc,我的商就存储在ax中,直接赋值cx,利用jcxz则可判断,而我们现在遇到的是32位的商,如何解决?
按位运算:
因为我们要判断ax和dx同时为0才做跳转,但此时我们目前能接触到的操作就只有一个jcxz,这就想到了按位运算,而且这里还应该得是按位与运算,该如何实现这个逻辑呢?
mov cx, ax
jcxz set
mov cx, dx
jcxz set
这个逻辑,显然是两个判断,有一个满足条件则跳转,当然是或关系啦
与操作应该怎么写?嵌套判断?但是你得知道jcxz指令不会将下一条指令的IP入栈,这条路子暂时不考虑,于是有了另一个思路
mov cx, ax
or cx, 0
or cx, dx
jcxz set
这样利用与零的关系,只有俩同时为0这种情况才会跳转
show_str(显示number字段中的字符串)
show_str: ;X参数cx输入字节 ;直接输出number字段中的数值 push ax push bx ;这里有bl暂存颜色 push cx ;cl暂存字符,这俩都不能删 push es push ds push si push di mov ax, number mov ds, ax ;mov si, 1500 ;指定位置 mov di, 0 ;字符串起始位置 mov bl, 00001011B ;指定颜色,默认 mov ax, 0b800h mov es, ax ;输出位置 mov ch, 0 forward: mov cl, ds:[di] jcxz exit mov es:[si], cl mov es:[si+1], bl add si, 2 inc di jmp forward exit: pop di pop si pop ds pop es pop cx pop bx pop ax ret
这个算是比较好实现的,注意参数的暂存,和栈的使用
str_store(字符输入到number字段)
str_store: ;输入;字码,ax低16,dx高16 ;输出:number字段 push ds mov bx, number mov ds, bx mov ds:[0], ax mov ds:[2], dx mov byte ptr ds:[4], 0 ;结束符号 pop ds ret
为了是结构统一,因为题目中给的年份是4字节的字符形式,故构造一个子程序,将这4个字节输入到number字段
然后就是几个用于显示的子程序
这几个子程序感觉都像是无参的函数一样,只是执行操作
show_year: ;输入:年份数据段 ;将内容输出到屏幕 push ax push cx push dx push si push di mov si, 500 ;指定位置 mov di, 0 mov cx, 21 total_year: mov ax, ds:[di] mov dx, ds:[di+2] call str_store call show_str add di, 4 add si, 160 loop total_year pop di pop si pop dx pop cx pop ax ret
show_summ: ;输入总收入字段 ;输出数据到屏幕 push ax push cx push si push di mov si, 530 mov di, 0 mov cx, 21 total_summ: mov ax, ds:[84+di] mov dx, ds:[84+di+2] call dtoc_pro call show_str add di, 4 ;sub si, 0 ;这里不需要减去偏移量,因为都是从0开始 add si, 160 loop total_summ pop di pop si pop cx pop ax ret
show_ne: ;输入人数字段 ;输出数据到屏幕 push ax push cx push si push di mov si, 570 mov di, 0 mov cx, 21 total_ne: mov ax, ds:[168+di] call dtoc call show_str add di, 2 ;sub si, 0 ;这里不需要减去偏移量,因为都是从0开始 add si, 160 loop total_ne pop di pop si pop cx pop ax ret
show_av: ;输入数据段:总收入和人数 ;输出平均值(取整) push ax push bx push cx push dx push di push si mov si, 610 mov bx, 0 mov di, 0 mov cx, 21 total_av: push cx mov ax, ds:[84+bx] mov dx, ds:[84+bx+2] add bx, 4 mov cx, ds:[168+di] add di, 2 call div_dw call dtoc_pro call show_str add si, 160 pop cx loop total_av pop si pop di pop dx pop cx pop bx pop ax ret
整体代码附下
assume cs:code, ds:data, ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984','1985'
db '1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514,345980
dd 590827,803530,1183000,1843000,2758000,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
number segment
db 1024 dup(0)
number ends
stack segment stack ;!!!事实证明,后面这个stack加上可以自动赋值sp,不加默认sp从0开始
db 1024 dup(0)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 1024 ;养成习惯,数据段及时初始化
call clear_screen
call show_year
call show_summ
call show_ne
call show_av
mov ax, 4c00h
int 21h
;=================Function=================
;==================================
show_year:
;输入:年份数据段
;将内容输出到屏幕
push ax
push cx
push dx
push si
push di
mov si, 500 ;指定位置
mov di, 0
mov cx, 21
total_year:
mov ax, ds:[di]
mov dx, ds:[di+2]
call str_store
call show_str
add di, 4
add si, 160
loop total_year
pop di
pop si
pop dx
pop cx
pop ax
ret
;==================================
show_summ:
;输入总收入字段
;输出数据到屏幕
push ax
push cx
push si
push di
mov si, 530
mov di, 0
mov cx, 21
total_summ:
mov ax, ds:[84+di]
mov dx, ds:[84+di+2]
call dtoc_pro
call show_str
add di, 4
;sub si, 0 ;这里不需要减去偏移量,因为都是从0开始
add si, 160
loop total_summ
pop di
pop si
pop cx
pop ax
ret
;==================================
show_ne:
;输入人数字段
;输出数据到屏幕
push ax
push cx
push si
push di
mov si, 570
mov di, 0
mov cx, 21
total_ne:
mov ax, ds:[168+di]
call dtoc
call show_str
add di, 2
;sub si, 0 ;这里不需要减去偏移量,因为都是从0开始
add si, 160
loop total_ne
pop di
pop si
pop cx
pop ax
ret
;==================================
show_av:
;输入数据段:总收入和人数
;输出平均值(取整)
push ax
push bx
push cx
push dx
push di
push si
mov si, 610
mov bx, 0
mov di, 0
mov cx, 21
total_av:
push cx
mov ax, ds:[84+bx]
mov dx, ds:[84+bx+2]
add bx, 4
mov cx, ds:[168+di]
add di, 2
call div_dw
call dtoc_pro
call show_str
add si, 160
pop cx
loop total_av
pop si
pop di
pop dx
pop cx
pop bx
pop ax
ret
;==========================================================================
div_dw:
;(输入):ax低16位,dx高16位,cx为16位除数
;(输出):dx结果高16位,ax结果低16位,cx为余数
push bx
mov bx, ax ;暂存ax,低位
mov ax, dx ;设置被除数
mov dx, 0 ;高位置为0
div cx ;高16作除法,余数dx并到低16位,这里无需改动,dx也是除法的高位
push ax ;暂存高16位除法结果中的商
mov ax, bx
div cx ;低16位除法,结果,ax为商,dx为余数
mov cx, dx
pop dx ;将暂存的高位ax,置于dx
pop bx
ret
;==================================
dtoc_pro:
;输入ax低16,dx高16
;内容输出到number字段
;注意,检测商为0才跳出
push bx
push cx
push si
push ds
mov si, 0 ;记录次数
mov bx, number
mov ds, bx ;目标地址
in_num_pro:
mov cx, 10 ;除数10
call div_dw ;返回余数cx
push cx ;保存余数
inc si
push cx
mov cx, ax ;判断商为0
or cx, 0
or cx, dx
jcxz in_num_pro_exit ;商为0,跳转,开始存入number字段
pop cx
jmp in_num_pro
in_num_pro_exit:
pop bx ;!!!!!!!!!!!注意多余的push要及时出栈,影响数据排布!!!!
mov cx, si
mov si, 0
mov bx, 0
set_num_pro:
pop bx ;写入目标地址操作
add bx, 30h
mov ds:[si], bl
inc si
loop set_num_pro
mov byte ptr ds:[si], 0 ;末尾添0,表示字符串结束
pop ds
pop si
pop cx
pop bx
ret
;==================================
dtoc:
;X输入ax,(16位)
;结果保存在number字段
;注意,检测商为0才跳出!!
push bx
push cx
push dx
push ds
push si
mov bx, number
mov ds, bx ;目标地址
mov si, 0 ;偏移指针,个数
mov bx, 10 ;除数
mov dx, 0 ;32位除法,但这里只有ax参与,dx得置0
in_num:
div bx
push dx
inc si
mov cx, ax ;这里没有loop循环,cx可放心使用
jcxz in_num_exit
mov dx, 0
jmp in_num
in_num_exit:
mov cx, si
mov si, 0
mov bx, 0
set_num:
pop bx
add bx, 30h
mov ds:[si], bl
inc si
loop set_num
mov byte ptr ds:[si], 0 ;末尾添0,表示字符串结束
pop si
pop ds
pop dx
pop cx
pop bx
ret
;==================================
str_store:
;输入;字码,ax低16,dx高16
;输出:number字段
push ds
mov bx, number
mov ds, bx
mov ds:[0], ax
mov ds:[2], dx
mov byte ptr ds:[4], 0 ;结束符号
pop ds
ret
;==================================
show_str:
;X参数cx输入字节
;直接输出number字段中的数值
push ax
push bx ;这里有bl暂存颜色
push cx ;cl暂存字符,这俩都不能删
push es
push ds
push si
push di
mov ax, number
mov ds, ax
;mov si, 1500 ;指定位置
mov di, 0 ;字符串起始位置
mov bl, 00001011B ;指定颜色,默认
mov ax, 0b800h
mov es, ax ;输出位置
mov ch, 0
forward:
mov cl, ds:[di]
jcxz exit
mov es:[si], cl
mov es:[si+1], bl
add si, 2
inc di
jmp forward
exit:
pop di
pop si
pop ds
pop es
pop cx
pop bx
pop ax
ret
;==================================
clear_screen:
;无输入
;执行清屏操作
push bx
push cx
push es
push si
mov bx, 0b800h
mov es, bx
mov cx, 4096
mov si, 0
swap: mov byte ptr es:[si], 0
inc si
loop swap
pop si
pop es
pop cx
pop bx
ret
;==================================
code ends
end start