汇编语言实验十完整代码和详细解析
建议先自己思考问题的答案,不懂则返回看书
扩展建议: 【非必要内容,个人经验感悟】
从这儿开始,我们正式接触子程序,主要是通过 call和 ret 来实现的。这样就涉及到了入口参数问题,提个写代码风格习惯的建议,只写必要的入口参数,尽量简化参数,为了更好的移植性。
例如写一个清屏函数,它的功能是清除屏幕上的所有字符,就是把相应内存的数据都写为零,它不需要任何参数,随时随地,只要 call clear_screen (函数名),就可以实现此功能, 移植性非常好。如果写一个显示字符串的函数 ,必要的入口参数有行、列、颜色,可以规定此函数的入口参数,用dh 表示列,dl 表示行 ,cl 表示颜色,显示以零结尾的字符串,这样的话它的移植性也是很好的。方便以后的使用。
1、显示字符串
要求: 在指定的位置,用指定的颜色,显示用0结束的字符串。行号,列号,我修改过。可能参数值不太一样,参数值自己设置
;在指定的位置,用指定的颜色,显示用0结束的字符串
assume cs:code
data segment
db 'Hello world',0 ;修改成welcome to masm 也是可以的,通用,可移植性好。
data ends
code segment
start:
mov dh,24 ;dh 行号,0-24
mov dl,8 ;dl 列号,0-79
mov cl,1 ;cl 颜色
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向字符串首地址
call show_str
mov ax,4c00h
int 21h
show_str:
push dx
push ax
push di
push es
push cx
push ds
push si
;实际行号
mov ax,160
mul dh ;行号偏移地址存在al中
mov dh,0
;实际列号
dec dl
add dl,dl ;列偏移地址存在dl中
add ax,dx
mov di,ax ;总体偏移地址在di中
mov ax,0b800h
mov es,ax ;所以es:[di]指向显存中的第一个位置
mov ah,cl ;颜色存在ah中
mov cx,0
s:
mov al,ds:[si]
mov cl,al
jcxz over
mov es:[di],ax
inc si
add di,2
jmp short s
over:
pop si
pop ds
pop cx
pop es
pop di
pop ax
pop dx
ret
code ends
end start
此代码在DOS下的运行结果截图:
2、解决除法溢出的问题
a、主要是表达出这个计算公式,X65536=X10000H,将十六进制的X左移四位。
b、另外一个可能忽视的点,当子程序返回后,相应的值用ax ,cx ,dx 存储,也就是ret执行后,已经是存储好了,所以设置好相应的值后,再ret返回。
assume cs:code
data segment
dw 8 dup(0)
data ends
code segment
start:
; mov ax,data
; mov ds,ax
mov ax,4240h ;被除数低16位
mov dx,000fh ;被除数高16位
mov cx,0ah ;除数
call divdw
mov ax,4c00h
int 21h
divdw:
push ax
mov ax,data
mov ds,ax
mov ax,dx ;被除数:dx存放高位,ax存放低位
mov dx,0
div cx ;ax存放着商,dx存放着余数
mov ds:[0],ax ;左侧数据的高位,也是所求表达式的商的高位
;对与右侧数据而言,余数dx的值为高位数据,L(原始被除数低16的值)
;仅为直观 mov dx,dx
pop ax ;右侧数据的被除数的低位
push ax ;再恢复元数据
div cx ;ax存放着商,dx存放着余数
mov ds:[2],ax ;此内存单元的值为所求表达式的商的低位
mov ds:[4],dx ;此内存单元的值为所求表达式的余数
pop ax
mov ax, ds:[2]
mov dx, ds:[0]
mov cx, ds:[4]
ret
code ends
end start
此程序在DOSBox下的运行结果:
3、数值显示
将data段中的数据以十进制形式显示出来,8行,3列,绿色。显示比较容易,直接用之前写好的showstr 函数。关键呢就是把数据“分开”,还是老祖宗的办法,除k取余法!
;将data段中的数据以十进制形式显示出来,8行,3列,绿色
assume cs:code
data segment
dw 123,12666,1,8,3,38
data ends
out segment
db 32 dup (0)
out ends
stack segment
dw 24 dup (0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov di,0
mov ax,stack
mov ss,ax
mov sp,48
mov ax,out
mov es,ax
mov si,0
mov cx,6
css:
call divw
dec cx
jcxz delete
inc cx
jmp next
delete:
inc cx
dec si ;divw函数结束,指向out段中最后一个逗号
mov al,0
mov es:[si],al ;将该逗号去掉,将值修改为0,即字符串结束标志
next:
loop css
mov dh,8 ;在8行3列显示为绿色
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
divw: ;将data中的每一个十进制数值拆分存储在out段中
;12666=317aH,需要16位除法,段中数据无溢出
;16位除法,被除数32位,dx高位,ax低位,ax商,dx余数
push ax
push bx
push cx
push dx
push es
mov cx,0
mov ax,ds:[di] ;低位放data中数据
mov bx,10 ;除数一直为10
divnei:
mov dx,0 ;高位放0
div bx ;ax商,dx余数
add dl,30h
push dx ;先将余数存放起来(逆序)
inc cx ;用cx记录数据的位数,为pop计数
push cx ;A 将该计数cx先存放,防止jcxz
mov cx,ax
jcxz divneiover
pop cx ;内部未结束,对应A处的push cx,继续计数
jmp short divnei
divneiover: ;一个数据分离完毕,进行存放
;cx已经对数据的位数计数,直接使用
pop cx ;内部结束,对应A处的push cx,结束计数
; mov cx,cx
divcun:
pop ax
mov es:[si],al
inc si
loop divcun
;一个数据处理完毕,隔一个逗号
mov al,2ch
mov es:[si],al ;逗号的ascii是2ch
inc si
;一个数据处理完毕,loop到下一个数据,divwai开始
inc di
inc di ;word型数据,加两字节
;数据处理完毕,返回原程序处
pop es
pop dx
pop cx
pop bx
pop ax
ret
show_str:
push dx
push ax
push di
push es
push cx
push ds
push si
mov ax,out
mov ds,ax
mov si,0 ;ds:si指向字符串首地址
;实际行号
inc dh ;不太懂,可能是因为有第0行??
mov ax,160
mul dh ;行号偏移地址存在al中
mov dh,0
;实际列号
dec dl
add dl,dl ;列偏移地址存在dl中
add ax,dx
mov di,ax ;总体偏移地址在di中
mov ax,0b800h
mov es,ax ;所以es:[di]指向显存中的第一个位置
mov ah,cl ;颜色存在ah中
mov cx,0
s:
mov al,ds:[si]
mov cl,al
jcxz over
mov es:[di],ax
inc si
add di,2
jmp short s
over:
pop si
pop ds
pop cx
pop es
pop di
pop ax
pop dx
ret
code ends
end start
此代码在DOSBox下的运行结果: