在汇编源程序中,我们一般会使用标号来指明某段功能代码的开始地址,同样,我们也可以使用标号来表示我们定义的数据开始的偏移地址,如下:
assume cs:code
code segment
;标号a,b指明两段数据开始的偏移地址
a: db 1,2,3,4,5,6,7,8
b: db 8 dup(0)
start:
mov ax, cs
mov ds, ax
mov es, ax
;获取标号a,b的偏移地址
mov si, offset a
mov di, offset b
;把标号a开始的数据传送到标号b开始的内存单元处
mov cx, 8
rep movsb
mov ax, 4c00h
int 21h
code ends
end start
同时,对于数据来说,有些是字节型,有些是字型,上述程序中定义的标号只能表示数据开始的偏移地址,无法表示该处的数据类型,汇编中提供了以下方式使用标号既可以表示偏移地址,也可以表示数据单元的类型:
assume cs:code
code segment
;标号a,b指明两段数据开始的偏移地址和数据类型
a db 1,2,3,4,5,6,7,8
b db 8 dup(0)
c db 8 dup(0)
start:
mov ax, cs
mov ds, ax
mov es, ax
;获取标号a,b的偏移地址
mov si, offset a
mov di, offset b
;把标号a开始的数据传送到标号b开始的内存单元处
mov cx, 8
rep movsb
;也可以使用以下方法来把a处的数据传送到c处
mov cx, 8
mov si, 0
s:
;此处a[si]相当于cs:[a + si], c[si]相当于cs:[c + si]
;即表示了单元地址,同时也表示了该数值为字节型
mov al, a[si]
mov c[si], al
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end start
通过 debug 查看 b,c 处都写入了 a 处开始的数据:
通过以上程序,我们可以发现,对于标号a、b、c的使用并没有冒号,这个时候,a、b、c就有了两个含义,一个是偏移地址,一个是所定义数据的类型,所以也就代表了一个内存单元。我们可以通过以下方式来访问内存单元:
a ;相当于cs:[a],即cs:[0]
b[1];相当于cs:[b + 1],即cs:[10H + 1]
之所以段寄存器是用 cs,是因为该数据是在 code 段中定义,而 cs:code 把 code 段和 cs关联。所以,当我们使用此功能时,需要把标号所在的段和段寄存器进行关联,如,在数据段中定义:
assume cs:code, es:data ;data段需要和某一段寄存器关联
data segment
;标号a,b指明两段数据开始的偏移地址和数据类型-字节
a db 1,2,3,4,5,6,7,8
b db 8 dup(0)
data ends
code segment
start:
mov ax, data
mov es, ax
mov cx, 8
mov si, 0
s:
;此处a[si]相当于es:[a + si], c[si]相当于es:[c + si],代表一个字节单元
mov al, a[si]
mov b[si], al
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end start
通过debug运行可以看到:
刚装载运行时,可以查看定义的内存单元数据:
当执行到循环处时,可以看到段寄存器为关联的 ES :
结束后查看数据的内存单元:
上述定义和使用标号的方式,称之为直接定址,通过该种方式,可以很方便的操作内存单元。
另外,对于db、dw等定义数据的指令,也可以使用标号,把标号的偏移地址定义到内存中:
assume cs:code, es:data
data segment
a dw exit
data ends
code segment
start:
mov ax, data
mov es, ax
mov ax, a
mov bx, offset exit
exit:
mov ax, 4c00h
int 21h
code ends
end start
通过debug可以看到,ax 和 bx 存放了标号 exit 处的偏移地址:
通过上面的方式,我们可以很轻易的定义多个子程序并进行调用,如:
提供以下功能:
- 设置屏幕的背景色(按 0 键)
- 设置屏幕字体颜色(按 1 键)
代码如下:
assume cs:code, es:data
;存放子程序的偏移地址
data segment
function dw backgroup, color
data ends
code segment
start:
mov ax, data
mov es, ax
mov dh, 00000000B
mov dl, 00000000B
key:
;读取键盘输入,当输入0时,改变背景色,当输入1时,改变字体颜色
mov ah, 0
int 16h
;回车键的扫描码为1CH
cmp ah, 1ch
je exit;回车时退出
;0~9的ASCII码为30H~39H
;输入的不是0或1时,循环输入
cmp al, 31H
ja key
cmp al, 30H
jb key
;输入的时0或1时,调用子程序
sub al, 30H
mov ah, 0
mov bx, ax
add bx, bx
call function[bx]
;继续键盘输入
jmp key
;改变背景色
backgroup:
mov ax, 0b800h
mov ds, ax
mov si, 1
mov cx, 2000
add dh, 00010000B
and dh, 01110000B
s:
mov byte ptr ds:[si], dh
add si, 2
loop s
ret
;改变字体颜色
color:
mov ax, 0b800h
mov ds, ax
mov si, 1
mov cx, 2000
add dl, 00000001B
and dl, 00000111B
s1:
mov byte ptr ds:[si], dl
add si, 2
loop s1
ret
exit:
mov ax, 4c00h
int 21h
code ends
end start
编译链接文件后,通过 dosbox 直接运行 exe 文件,输入0或1键即可看到相应的功能效果:
当子功能较多时,根据这种方法可以实现灵活的程序调用,同时又便于我们功能的扩充,如果加入一个新的功能子程序,只需要在地址表function中添加他的入口地址即可。