描述了单元长度的标号
程序中的标号仅仅表示了内存单元的地址。
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
start: 和 s: 仅仅表示了内存单元地址 这里的标号 a 和 b 后面没有冒号,它们是同时描述内存单元地址和单元长度。标号 a 描述了地址 code:0 和从这个地址开始,以后的内存单元都是字节单元;标号 b 描述了地址 code:8 和从这个地址开始,以后的内存单元都是字单元。
|
指令:mov ax , b
相当于:mov ax , cs:[8]
指令:mov b , 2
相当于:mov word ptr cs:[8] , 2
指令:inc b
相当于:inc word ptr cs:[8]
指令:mov al , a[si]
相当于:mov al , cs:0[si]==mov al , cs:[si].0
如果立即数不在最开头,就要在数字前加一个 '.' ,没有CS默认就是DS
指令:mov al , a[3]
相当于:mov al , cs:0[3]
指令:mov al , a[bx+si+3]
相当于:mov al , cs:0[bx+si+3]
mov al , b 会引起错误,应为 b 代表的内存单元是字单元,而 al 是8位寄存器。 |
; 对比 assume cs:code
code segment
a: db 1 , 2 , 3 ,4 , 5 , 6 , 7 , 8
b: dw 0
start: mov si , offset a
mov bx , offset b
mov cx , 8
s: mov al , cs:[si]
mov ah , 0
add cs:[bx] , ax
inc si
loop s
mov ax , 4c00h
int 21h
code ends
end start
带冒号的标号,因为不描述单元长度,所以只能程序员使用的时候告诉编译器长度。 |
;检测点16.1
assume cs:code
code segment
a dw 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8
b dd 0
start: mov si , 0
mov cx , 8
s: mov ax , a[si]
add word ptr b , ax
adc word ptr b[2] , 0
add si , 2 ; dw,所以要加2
loop s
mov ax , 4c00h
int 21h
code ends
end start
24h=36
|
在其他段中使用数据标号
一般来说,我们不在代码段中定义数据,而是将数据定义到其他段中,在其他段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。
注意:在后面加有 " : " 的地址标号,只能在代码段中使用,不能在其他段中使用。
; 将data段中 a 标号处的 8 个数据累加,结果存储到 b 标号处的字中; assume cs:code , ds:data ; 这里必须ds:data,不然s:处不能编译通过
data segment
a db 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ; a: 就不行
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] ;ds:data ,所以a的段地址是ds
mov ah , 0
add b , ax
inc si
loop s
mov ax , 4c00h
int 21h
code ends
end start
想在代码段中,直接用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来,否则编译器在编译的时候,无法确定标号的段地址在哪一个基础寄存器中。 这种联系是编译器需要的,绝对不是说,我们因为编译器的工作需要,用assume指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。我们在程序中还要用指令对段寄存器进行设置。
|
程序中我们在 code 段中用 data 段中的数据标号 a、b 访问数据,则必须用assume将一个寄存器和data段相联。在程序中,我们用 ds 寄存器和 data 段相连,则编译器对相关指令的编译如下: 指令:mov al , a[si]
编译为:mov al , [si+0]
指令:add b , ax
编译为:add [8] , ax
因为这些实际编译出的指令,都默认所访问的单元的段地址在 ds 中,而实际要访问的段为data,所以要访问正确,在这些指令执行前,ds 中必须为 data 段的段地址。
mov ax , data
mov ds , ax
设置 ds 指向 data 段
|
; 我们可以将标号当做数据来定义,此时,编译器将标号所代表的地址当做数据的值。 data segment
a db 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8
b dw 0
c dw a , b
data ends
数据标号 c 处存储的两个字型数据为标号a、b的偏移地址;相当于:
data segment
a db 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8
b dw 0
c dw offset a , offset b
data ends
data segment
a db 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8
b dw 0
c dd a , b
data ends
数据标号 c 处存储的两个双字节数据为标号 a 的偏移地址和段地址、标号 b 的偏移地址和段地址;相当于:
data segment
a db 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8
b dw 0
c dw offset a , seg a , offset b , seg b
data ends
seg 操作符,功能为取得某一标号的段地址
|
assume cs:code , es: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 es , ax
mov si , 0
mov cx ,8
s: mov al , a[si] ; data 段已经和 es 关联,所以这里默认就是es,而不是ds了
mov ah , 0
add b , ax
inc si
loop s
mov ax , 4c00h
int 21h
code ends
end start
|
直接定址表
利用表,在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一个集合中的对应数据。这样做的目的有三个: 1、为了算法的清晰和简洁。 2、为了加快运算速度。 3、为了使程序易于扩充。 ;编写子程序,以16进制的形式在屏幕中间显示给定的byte型数据 assume cs:code , ds:data
data segment
num1 db 22h
data ends
code segment
start: mov ax , data
mov ds , ax
mov si , 0 ; 就一个数据,不要这步操作;直接mov al , num1也行
mov al , num1[si]
mov ah , 0
call showbyte
mov ax , 4c00h
int 21h
|
showbyte: jmp short show
table db '0123456789ABCDE'
show: push bx
push es
push cx
mov ah , al
mov cl , 4
shr ah , cl
and al , 00001111b
mov bl , ah
mov bh , 0
mov ah , table[bx] ; 用高4位来取得相对于table的偏移,取得对应的字符
mov bx , 0b800h ; 显示十位上的数据
mov es , bx
mov byte ptr es:[160*12+40*2] , ah
mov byte ptr es:[160*12+40*2+1] , 2
mov bl , al
mov bh , 0
mov al , table[bx] ; 用低4位的值作为相对于table的偏移,取得对应的字符
mov byte ptr es:[160*12+40*2+2] , al
mov byte ptr es:[160*12+40*2+3] , 2
pop cx
pop es
pop bx
ret
code ends
end start
|
通过给出的数据进行计算或比较而得到结果的问题,转化为用给出的数据作为查表的依据,通过查表得到结果的问题。具体的查表方法,是用查表的依据数据,直接计算出所要查找的元素在表中的位置。像这种可以通过依据数据,直接计算出所要找的元素的位置的表,就叫做:直接定定址表。
程序入口地址的直接定址表
可以在直接定址表中存储字程序的地址,从而方便的实现不同子程序的调用。
实现一个子程序 setscreen,为显示输出提供如下功能: ① 清屏 ② 设置前景色 ③ 设置背景色 ④ 向上滚动一行 入口参数说明: ① 用 ah 寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行; ② 对于2、3号功能,用 al 传送颜色值,(al)∈{0,1,2,3,4,5,6,7} 功能实现方法: ① 清屏:将显存中的当前屏幕中的字符设为空格符; ② 设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位; ③ 设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位; ④ 向上滚动一行:一次将第n+1行的内容复制到第n行处;最后一行为空。 |
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 cx
pop bx
ret
|
sub2: push bx
push cx
push es
mov bx , 0b800h
mov es , bx
mov bx , 1
mov cx , 2000
sub2s: and byte ptr es:[bx] , 11111000b ; 重置前景色
or es:[bx] , al
add bx , 2
loop sub2s
pop es
pop cx
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: and byte ptr es:[bx] , 10001111b
or es:[bx] , al
add bx , 2
loop sub3s
pop es
pop cx
pop bx
ret
|
sub4: push cx
push si
push di
push es
push ds
mov si , 0b800h
mov es , si
mov ds , si
mov si , 160
mov di , 0
cld
mov cx , 24
sub4s1: push cx
mov cx , 160
rep movsb
pop cx
loop sub4s
mov cx , 80
mov si , 0
sub4s2: mov byte ptr [160*24+si] , ' '
add si , 2
loop sub4s
pop ds
pop es
pop di
pop si
pop cx
ret
|
将这些功能的入口地址存储在一个表中,它们在表中的位置和功能号相对应。对应关系:功能号*2=对应的功能子程序在地址表中的偏移。
setscreen: jmp short set ; 也可以设置一个一个比较,0,1,2,3,4不在这些范围内就返回
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
setscreen: cmp ah , 0 ; 对比程序
je do1
cmp ah , 1
je do2
cmp ah , 2
je do3
cmp 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
|
//很明显最后一种通过比较功能号进行转移的方法,程序结构比较混乱,不利于功能的扩充。eg:在 setscreen 中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。
用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。