这几天一直在玩,要抓紧学完,去学别的东西哦
直接定址表
学习如何有效的组织数据,以及相关的编程技术
描述了单元长度的标号
我们先以一个程序为例
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
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
这里的 a b start s code都是标号。这些标号仅仅表示了内存单元的地址
但是我们还有一种标号,这种标号不仅表示内存单元的地址,同时还表示了内存单元的长度
上面的程序可以写成下面的形式
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
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
在code段中使用的标号a,b后面没有 :,他们是同时描述内存地址和长度的标号,例如:
- 标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元
- 标号b,描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元
可见,使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。我们将这种标号称为 数据标号
在其他段中使用数据标号
指定段寄存器
在其他段中,也可以使用数据标号来描述存储数据的单元的地址和长度
不过要注意,在后面加有":"的地址标号,只能在代码段中使用,不能在其他段中使用
比如我们以一个累加程序为例:
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 bx,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指令将段寄存器和某个段相联系,段寄存器中就真的回存放该段的地址。我们在程序中,仍然需要指令对段寄存器进行设置
将标号当作数据定义
可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。比如:
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
再比如:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends
数据标号c处存储的两个双字型为标号a的偏移地址和段地址,标号b的偏移地址和段地址。相当于:
c dw offset a,seg a,offset b,seg b
这里的seg操作符,功能为取得某一标号的段地址
直接定址表
我们注意到数值0~15和字符"0"~"F"之间并没有直接的映射关系,所以我们需要再他们之间建立新的映射关系
数值0~9和字符"0"~"9"之间的映射关系最明显,有
数值 + 30H = 对应字符的ASCII值数值10~15和字符"A"~"F"之间的映射关系则是,
数值 + 37H = 对应字符的ASCII值
具体的做法是,建立一张表,表中依次存储字符"0"~“F”,我们可以通过数值0~15直接查找到对应的字符
assume cs:code
stack segment
dw 16 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,3AH
call show
mov ax,4c00H
int 21h
showbyte:
jmp short show
table db '0123456789ABCDEF' ;字符表
show:
push bx
push es
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1 ;取高四位
and al,00001111b ;取低四位
mov bl,ah
mov bh,0
mov ah,table[bx]
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
code ends
end start
这张表定义后可以实现映射操作,当我们向ax中传输一个数值时,它会返回这个数值到显示器上
这种映射关系一般用作以下几种用途:
- 为了算法的清晰和简洁
- 为了加快运算速度
- 为了使程序易于填充
接下来编写一个子程序
assume cs:code
stack segment
dw 16 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,120
call show
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
;用角度/30作为相对于table的偏移值,取得对应的字符串的偏移地址,放在bx中
mov ah,0
mov bl,30
div bl
mov bl,al
mov bh,0
add bx,bx ;注意每个标号实际上使双字节的
mov bx,table[bx]
;显示
mov si,160*12+40*2
shows:
mov ah,cs:[bx] ;此时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
上面这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称之为直接定址表
程序入口的直接定址表
我们可以在直接定址表中存储子程序的地址,从而方便的实现不同子程序的调用。
我们编写以下程序

得知需求之后,分析一下功能的实现:
- 清屏:讲显存中当前屏幕中的字符设置为空格符
- 设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0,1,2位
- 设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4,5,6位
- 向上滚动一行:依次将第n+1行的内容复制到第n行;最后一行为空
我们可以写出以下程序:
assume cs:code
stack segment
dw 32 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,32
mov ah,3
mov al,1
call setscreen
mov ax,4c00h
int 21h
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
;清屏
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 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld
mov cx,24
sub4s:
push cx
mov cx,160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1:
mov byte ptr [160*24+si],' '
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start
根据上面的程序需求即可修改对应的参数
最后的挑战
字符串打印

程序如下,利用栈和int 16h模拟了键盘的输入和撤回:
assume cs:code
stack segment
dw 128 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,256
call clean
mov dh,12
mov dl,25
call getstr
mov ax,4c00h
int 21h
charstack:
jmp short charstart
table dw charpush,charpop,charshow
top dw 0 ;栈顶
charstart:
push bx
push dx
push di
push es
cmp ah,2
ja sret
mov bl,ah
mov bh,0
add bx,bx ;表项是二字节的,所以偏移位置要左移一位
jmp word ptr table[bx]
charpush:
mov bx,top
mov [si][bx],al
inc top
jmp sret
charpop:
cmp top,0
je sret
dec top
mov bx,top
mov al,[si][bx]
jmp sret
charshow:
mov bx,0b800h
mov es,bx
mov al,160
mov ah,0
mul dh ;行号
mov di,ax
add dl,dl
mov dh,0 ;列号
add di,dx
mov bx,0
charshows:
cmp bx,top
jne noempty
mov byte ptr es:[di],' '
jmp sret
noempty:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' '
inc bx
add di,2
jmp charshows
sret:
pop es
pop di
pop dx
pop bx
ret
; bye:
; mov ax,4c00h
; int 21h
getstr:
push ax
getstrs:
mov ah,0
int 16h
; cmp al,1
; je bye
cmp al,20h
jb nochar ;字符码小于20h,说明不是字符
mov ah,0
call charstack ;字符入栈
mov ah,2
call charstack ;显示字符
jmp getstrs
nochar:
cmp ah,0eh ;BS的扫描码
je backspace
cmp ah,1ch ;Enter的扫描码
je enter
jmp getstrs
backspace:
mov ah,1
call charstack ;字符出栈
mov ah,2
call charstack ;显示字符
jmp getstrs
enter:
mov al,0
mov ah,0
call charstack ;0入栈
mov ah,2
call charstack ;显示字符
pop ax
ret
;清屏
clean:
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
cleans:
mov byte ptr es:[bx],' '
add bx,2
loop cleans
pop es
pop cx
pop bx
ret
code ends
end start