我们可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。我们看下面的问题。 实现一个子程序setscreen,为显示输出提供如下功能。
(1)清屏;
(2)设置前景色;
(3)设置背景色;
(4)向上滚动一行。```````````````````````````````````````````````````````````````````````````````````````
(1)清屏:将显存中当前屏幕中的字符设为空格符;
(2)设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0, 1, 2位;
(3)设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4. 5, 6位:
(4)向上滚动一行:依次将第n+1行的内容复制到第n行处;最后一行为空。
我们将这4个功能分别写为4个子程序,请读者根据编程思想,自行读懂下面的程序。
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s:mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cs
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s:mov byte ptr es:[bx],' 11111000b'
or es:[bx],al
loop sub2s
pop es
pop cs
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s:mov byte ptr es:[bx],' 11111000b'
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cs
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mos si,160 ;ds:si 指向第 n+1 行
mov di,0 ;ds:si 指向第 n 行
cld
mov cx,24 ;共复制24行
sub4s:push cx
mov cx,160
rep movsb ;复制
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1:mov byte ptr [160*24+si],' ' ;最后一行清空
add si,2
loop sub4s1
pop de
pop es
pop di
pop si
pop cx
ret
我们可以将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应。对应关系为:功能号*2=对应的功能子程序在地址表中的偏移。程序如下:
setscreen: jmp short set
table dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3 ;判断功能号是否大于3
ja,sret
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能号计算对应子程序在table表中的偏移
call word ptr table[bx] ;调用对应的功能子程序
sret: pop bx
ret
当然,我们也可以将子程序setscreen如下实现
setscreen: cmp ah,0
je do1
cmp ah,1
je do2
cmp ah,2
je do3
dmp ah,3
je do4
jmp short sret
do1: call sub1
jmp short sret
do2: call sub2
jmp short sret
do3: call sub3
jmp short sret
do4: call sub4
sret: ret
显然,用通过比较功能号进行转移的方法,程序结构比较混乱,不利于功能的扩充。比如说,在setscreen中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。
用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。
