我们可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。我们看下面的问题。 实现一个子程序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中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。
用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。