第16章 直接定位表
16.1 描述了单元长度的标号
我们常用offset来获取标号的内存地址。
offset a,reg a,前者是获取a的偏移地址,后者是获取a的段地址。
将它们放在相关寄存器当中,进行数据转移等有关操作。
现在,我们有了另一种写法:
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start: mov si,0
mov cx,0
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
这样,我们的标号就相当于数组名,然后之后就是后面的元素,a[si]
,这种写法是你应该要明确的。
而标号b描述的地址是 code:8,和从这个地址开始,以后的内存单元都是字单元。
mov b,2
相当于 mov word ptr cs:[8],2
,都是进行字操作的。
下面的指令则会引起编译错误:
mov al,b
16.2 在其他段中使用数据标号
一般来说,我们不在代码段中定义数据,而是将数据定义到其他段中。在其他段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。
注意,在后面加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用。
下面的程序,将data段中的a标号处的8个数据累加,结果存储到b标号处的字中。
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
注意,如果想在代码段中直接用数据标号访问数据,则需要用伪指令 assume 将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器。当然,这种联系是编译器需要的,但不是说,之后就不用操作了,你必须再手动将它们联系起来。
mov al,a[si],其实质就是 mov al,[si+0],因为a的地址为0.这种数组的思想你应该很明确的。
可以将标号当作数据来定义,此时,编译器将标号所表示的地址当做数据的值。
比如:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends
数据标号c处存储的两个字型数据为标号a,b的偏移地址。相当于:
c dw offset a,offset b
我们可以在c段中存储其段地址和偏移地址,相关操作如下:
c dw offset a,seg a,offset b,seg b
就是这个样子。
16.3 直接定址表
现在,我们讨论用查表的方法编写相关程序的技巧。
编程以十六进制的形式在屏幕中间显示给定的字节型数据。
注意,一个字节8位,而十六进制为4位,故一个字节应该分别表示两个数据。
利用移位等原理,可以将一个十六进制相关位数拆开。
这里直接使用字母表的形式,不多做介绍,看过之后就自然会明白。
子程序如下:
showbyte: jmp short show
table db '0123456789ABCDEF'
show: push bx
push es
push cx
mov ah,al
mov cx,4
shr ah,cx ;右移4位,ah中得到高4位的值
and al,00001111b ;al中为低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx];高4位的值作为相对table的偏移,取得对一个的字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0
mov al,table[bx];用低4位的值作为相对于table的偏移,取得对应的字符
mov es:[160*120+40*2+2],al
pop es
pop bx
ret
这个就是数组模型的利用,知道这么回事,利用偏移地址来实现,就是这个样子,很简单。
16.4 程序入口地址的直接定址表
我们可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。
我们看下面的问题:
实现一个子程序setscreen,为显示输出提供如下功能。
- 清屏;
- 设置前景色;
- 设置背景色;
- 向上滚动一行。
我们现在要在一个程序中,顺序调用这四个字程序,我们现在不去纠结其如何实现的,这些内容前面我们都实现过了,最后一个向上滚动一行就是把下面的数据全部复制到显示缓冲区,就是这个样子。
我们可以将这些功能字程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应。对应关系位:功能号*2=对应的功能子程序在地址表中的偏移。程序如下:
setscreen: jmp short set
table dw sub1,sub2,sub3,sub4
set: push bx
;判断功能号是否大于3,如果大于3则进行退出,这种操作是你需要明确的。
cmp ah,3
ja sret
mov bl,ah
mov bl,0
add bx,bx ;根据ah中的功能号计算对应子程序在table表中的偏移
call word ptr table[bx]
sret: pop bx
ret
当然,我们可以用一下程序来实现:
setcreen: cmp ah,0
je do1
cmp ah,2
je do2
cmp ah,3
je do3
cmp ah,4
je do4
jmp shor sret
do1: call sub1
jmp short sret
do2: call sub2
jmp short sret
do3: call sub3
jmp short sret
do4: call sub4
jmp shoer sret
sret: ret
这种方式,在我们之后编写子程序时,将会大大地节省我们的时间和精力,这种操作是你要明确的!