描述单元长度的标号
我们可以使用下面的标号来表示数据的开始:
···
code segment
a:db 1,2,3,4,5,6,7,8
b:dw 0
···
code ends
···
a,b都是代表对应数据的起始地址,但并不能判断数据的长度或类型。下面一段程序将a中的8个数累加存入b中:
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start 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
code段中a和b后并没有”:”号,这种写法同时描述内存地址和单元长度的标号。a描述了地址code:0和从这个地址开始后的内存单元都是字节单元,而b描述了地址code:8和从这个地址开始以后的内存单元都是字单元。所以b相当于CS:[8],a[si]相当于CS:0[si],使用这种标号,我们可以间接地访问内存数据。
其它段中使用数据标号
刚说的第一种标号即加”:”号的标号,只能使用在代码段中,不能在其他段中使用。如果想要在其它段中(如data段)使用标号可以使用第二种:
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
···
start mov ax,data
mov ds,ax
mov si,0
mov al,a[si]
···
如果想在代码段中直接使用数据标号访问数据,需要使用assume伪指令将标号所在段和一个寄存器联系起来,是让寄存器明白,我们要访问的数据在ds指向的段中,但编译器并不会真的将段地址存入ds中,我们做了如下假设之后,编译器在编译的时候就会默认ds中已经存放了data的地址,如下面的编译例子:
mov al,a[si]
编译为:mov al,[si+0]
可以看出编译器默认了a[si]在ds所在的段中。所以我们需要手工指定ds指向data:
mov ax,data
mov ds,ax
也可以这么使用:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c a,b
data ends
c处存放的是a和b的偏移地址,相当于c dw offset a,offset b。同理c dd a,b相当于c dw offset a,seg a,offset b,seg b即存的是a和b的段地址和偏移地址。
直接定址表
使用查表的方法编写相关程序,如输出一个字节型数据的16进制形式(子程序):
showbyte jmp short show
table db '0123456789ABCDEF'
show:push bx
push es
mov ah,al
she ah,1
she ah,1
she ah,1
she ah,1 ;右移四位,位移子程序限制使用的寄存器数,只能这么移
and al,00001111b
mov bl,al
mov bh,0
mov ah,table[bx] ;高四位作为相对于table的偏移,取得对应字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0
mov al,table[bx]
mov es:[160*12+40*2+2],al
pop es
pop bx
ret
可见我们直接使用需要的数值和地址的映射关系来寻找需要的数据。
程序入口地址的直接定址表
可以看书P296的例程,主要思想是,编写多个子程序实现不同功能,每个子程序有自己的标号,如sub1,sub2···等。将它们存在一个表中:
table dw sub1,sub2,sub3,sub4
然后按照之前的方法使用如:
setscreen:jmp short set
table dw sub1,sub2,sub3,sub4
set:push bx
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
call word ptr table[bx]
sret:pop bx
ret