1. 预备知识
- 用查表的方法可以简化程序的编写。如以十六进制的形式在屏幕中间显示给定的字节型数据。
一个字节需用两个十六进制码表示,程序使用 0~F 这 16 个字符来显示。如要在屏幕显示 2Bh,需将 2 映射为 ASCII 码 32h(2 + 30h),将 B 映射为 ASCII 码 42h(11 + 37h),二者的映射方式不同。因此,在实际映射时需对给定字符进行判断,然后再确定映射方式。
为了简化程序,我们应在数字 0~15 和 0~F 之间建立新的映射关系。具体做法是建立一张表,表中依次存储 0~F,然后通过数字 0~15 直接查找对应的字符。
table db '0123456789ABCDE'
子程序如下:
;al传入待显示数据
showbyte:
jmp short show
table db '0123456789ABCDE'
show:
push bx
push es ;保护现场
mov ah,al
mov cl,4
shr ah,cl ;右移4位得到高4位的值
and al,00001111b ;按位与操作得到低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;高4位值作为偏移取得对应字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
;显示高位字符
mov bl,al
mov bh,0
mov al,table[bx] ;低4位值作为偏移取得对应字符
mov es:[160*12+40*2+2],al
;显示低位字符
pop es
pop bx ;恢复现场
ret
整体代码为:
assume cs:code
code segment
start:
mov al,2Bh
call showbyte
mov ax,4c00h
int 21h
showbyte:
jmp short show
table db '0123456789ABCDE'
show:
push bx
push es ;保护现场
mov ah,al
mov cl,4
shr ah,cl ;右移4位得到高4位的值
and al,00001111b ;按位与操作得到低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;高4位值作为偏移取得对应字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
;显示高位字符
mov bl,al
mov bh,0
mov al,table[bx] ;低4位值作为偏移取得对应字符
mov es:[160*12+40*2+2],al
;显示低位字符
pop es
pop bx ;恢复现场
ret
code ends
end start
程序运行结果如下:
- 在上面的子程序中,使用表是为了算法的清晰和简洁。下面将介绍查表的另一个特点,加快运算速度。如编写一个子程序,计算 sin(x),x∈{0°, 30°, 60°, 90°, 120°, 150°, 180°}。
首先查看需要计算的 sin 的结果:
sin(0)=0
sin(30)=0.5
sin(60)=0.866
sin(90)=1
sin(120)=0.866
sin(150)=0.5
sin(180)=0
我们可以将上述各角度的 sin 值计算完成后存入表中,然后对输入角度除以 30 获得偏移,并取得对应的值。
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
;表存放各字符串对应的偏移
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
子程序如下:
showsin:
jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
;表存放各字符串对应的偏移
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
show:
push bx
push es
push si
mov bx,0b800h
mov es,bx
mov ah,0
mov bl,30
div bl ;计算除以30
mov bl,al ;AL存储除法运算的商
mov bh,0
add bx,bx ;表中的字段为双字类型
mov bx,table[bx]
mov si,160*12+40*2
shows:
mov ah,cs[bx]
cmp ah,0
je showret ;到达字符串结尾
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret:
pop si
pop es
pop bx
ret
整体代码为:
assume cs:code
code segment
start:
mov ax,3Ch ;传入角度为60°
call showsin
mov ax,4c00h
int 21h
showsin:
jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
;表存放各字符串对应的偏移
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
show:
push bx
push es
push si
mov bx,0b800h
mov es,bx
mov ah,0
mov bl,30
div bl ;计算除以30
mov bl,al ;AL存储除法运算的商
mov bh,0
add bx,bx ;表中的字段为双字类型
mov bx,table[bx]
mov si,160*12+40*2
shows:
mov ah,cs:[bx]
cmp ah,0
je showret ;到达字符串结尾
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret:
pop si
pop es
pop bx
ret
code ends
end start
程序运行结果如下:
- 我们可以在表中存储子程序的地址,从而方便地实现不同子程序的调用。
2. 实验任务
安装一个新的 int 7ch 中断例程,为显示输出提供如下功能子程序:清屏;设置前景色;设置背景色;向上滚动一行。入口参数说明:
用 ah 传递功能号:0 表示清屏,1 表示设置前景色,2 表示设置背景色,3 表示向上滚动一行
对于 2、3 号功能,用 al 传递颜色值,(al)∈{0,1,2,3,4,5,6,7}
首先分别实现四个子程序的内容。清屏,即将显存中当前屏幕中的字符设定为空格。
cls_screen:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000 ;当前屏幕含2000个字符
help:
mov byte ptr es:[bx],' '
;写入空格
add bx,2
loop help
pop es
pop cx
pop bx ;恢复现场
ret
设置前景色,颜色值存放在 AL 中。字符属性表示如下:
[7] [6 5 4] [3] [2 1 0]
闪烁 背景(RGB) 高亮 前景(RGB)
前景色对应于后三位,首先使用与运算清除字符属性区域的后三位 and 11111000b,然后将 AL 中的内容存入字符属性区域。
set_fore:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_fore:
and byte ptr es:[bx],11111000b
;清除后三位的值
or es:[bx],al
;设置前景色
add bx,2
loop help_set_fore
pop es
pop cx
pop bx ;恢复现场
ret
设置背景色,颜色值存放在 AL 中。背景色为第 2~4 位,和设置前景色类似。
set_back:
push bx
push cx
push es ;保护现场
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_back:
and byte ptr es:[bx],10001111b
;清空对应位
shl al,1
shl al,1
shl al,1
shl al,1
;左移4位,将AL的内容移到前4位
or es:[bx],al
;设置背景色
add bx,2
loop help_set_back
pop es
pop cx
pop bx ;恢复现场
ret
向上滚动一行,借助 rep movsb 指令每次传送一行数据,即 160 字节,共传送 24 行。
roll:
push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si ;ES和DS均指向显示缓冲区
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置传输方向为正
mov cx,24 ;共移动24行
help_roll:
push cx
mov cx,160
rep movsb ;源地址为ds:si,目的地址为es:di,共传送160字节
pop cx
loop help_roll
mov cx,80 ;每列共80个字符
mov si,0
help_help_roll:
mov byte ptr [160*24+si],' '
;最后一行使用空格填充
add si,2
loop help_help_roll
pop ds
pop es
pop di
pop si
pop cx
ret
然后将各子程序的入口地址存储在表中,并且它们在表中的位置与其功能号相对应,满足功能号*2=对应子程序的入口地址。
setscreen:
jmp short set
table:
dw cls_screen,set_fore,set_back,roll
set:
push bx
cmp ah,3 ;判断功能号是否大于3
ja setret
mov bl,ah
mov bh,0
add bx,bx ;根据功能号得到对应子程序在表中的偏移
call word ptr table[bx]
;根据偏移调用对应的子程序
setret:
pop bx
ret
用 AH 传送功能号、对于功能 2 和 3 使用 AL 传送颜色。整体代码为:
assume cs:code
code segment
start:
mov ah,1
mov al,2h
call setscreen
mov ax,4c00h
int 21h
setscreen:
jmp short set
table:
dw cls_screen,set_fore,set_back,roll
set:
push bx
cmp ah,3 ;判断功能号是否大于3
ja setret
mov bl,ah
mov bh,0
add bx,bx ;根据功能号得到对应子程序在表中的偏移
call word ptr table[bx]
;根据偏移调用对应的子程序
setret:
pop bx
ret
cls_screen:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000 ;当前屏幕含2000个字符
help_cls_screen:
mov byte ptr es:[bx],' '
;写入空格
add bx,2
loop help_cls_screen
pop es
pop cx
pop bx ;恢复现场
ret
set_fore:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_fore:
and byte ptr es:[bx],11111000b
;清除后三位的值
or es:[bx],al
;设置前景色
add bx,2
loop help_set_fore
pop es
pop cx
pop bx ;恢复现场
ret
set_back:
push bx
push cx
push es ;保护现场
mov cl,4
shl al,cl ;左移4位,将AL的内容移到前4位
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_back:
and byte ptr es:[bx],10001111b
;清空对应位
or es:[bx],al
;设置背景色
add bx,2
loop help_set_back
pop es
pop cx
pop bx ;恢复现场
ret
roll:
push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si ;ES和DS均指向显示缓冲区
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置传输方向为正
mov cx,24 ;共移动24行
help_roll:
push cx
mov cx,160
rep movsb ;源地址为ds:si,目的地址为es:di,共传送160字节
pop cx
loop help_roll
mov cx,80 ;每列共80个字符
mov si,0
help_help_roll:
mov byte ptr [160*24+si],' '
;最后一行使用空格填充
add si,2
loop help_help_roll
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start
0 号功能展示:
1 号功能展示:
2 号功能展示:
3 号功能展示:
3. 总结
- 直接定址表提供了一种快速访问标号或字符的方法,通过待访问元素的索引定位其偏移地址。
- 在包含多个子程序的程序中,直接定址表可以快速定位各子程序的偏移地址。实验展示了包含 4 个子程序的程序,通过直接定址表实现快速调用。