第十六章
16.1 描述了單元長度的標號
之前我們一直在代碼段中使用標號來標記指令、數據、段的起始地址
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
程序中的code、a、b、start都是標號,這些標號僅僅表示了內存單元的地址
還有一種標號,不但表示內存單元的地址,還表示了內存單元的長度,即表示在此標號處的單元,是一個字/字節/雙字單元
上面的程序也可以寫出這樣
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
標號a,描述了地址code:0以及從這個地址開始以後的內存單元都是字節單元
標號b,描述了地址code:8以及從這個地址開始,以後的內存單元都是字單元
這種標號可以代表一個段中的內存單元
; 兩者等價
mov ax,b
mov ax,cs:[8]
; 下面的指令會引起編譯錯誤,長度不匹配
mov al,b
; 兩者等價
mov al,a[si]
mov al,cs:0[si]
我們將這種包含單元地址和長度的標號稱爲數據標號
下面的程序將code段中a處的8個數據累加,結果存儲到b處的雙字中,補全程序
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 ; b雖然還包含着長度信息
adc word ptr b[2],0 ; 但其本身指的是cs:[16]這個內存單元
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
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
iont 21h
code ends
end start
如果想在代碼段中直接使用數據標號訪問數據,則需要用僞指令assume將標號所在的段和一個段寄存器聯繫起來,否則編譯的時候,無法確定標號的段地址在哪個寄存器中
在上面的程序中,我們用ds和data段關聯,則編譯器對相關指令的編譯如下
mov al,a[si] ; 編譯爲 mov al.[si+0]
add b,ax ; 編譯爲 add [8],ax
我們可以將標號當作數據來定義,此時,編譯器將標號所表示的地址當作數據的值
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends
; 相當於
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
; 相當於
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操作符的功能爲取得某一標號的段地址
16.3 直接定址表
編寫子程序,以十六進制的形式在屏幕中間顯示給定的字節型數據
一個字節需要兩個十六進制數碼來表示,所以我們將一個字節的高4位和低4位分開,然後分別得到對應的ASCII碼,但有一個問題
如2BH,2 + 30H = “2” 而11 + 37H = “B”
它們的映射關係是不同的
所以我們建立一張表,表中一次存儲字符“0”~“F”,通過數值直接查找到對應的字符
; 用al轉送要顯示的數據
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 ; 右移4位,ah中得到高4位的值
and al,00001111b ; al中爲低4位的值
mov bl,ah
mov bh,0 ; 高4位的值作爲對應table的位移
mov ah,table[bx] ; 取得對應字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0 ; 高4位的值作爲對應table的位移
mov al,table[bx] ; 取得對應字符
mov es:[160*12+40*2+2],al
pop es
pop bx
ret
利用表,在兩個數據集合之間建立一種映射關係,使算法更簡潔,運算更快,程序易於擴充
下例爲了加快運算速度而採用查表
編寫一個子程序,計算sin(x),x屬於{0,30,60,90,120,150,180},並在屏幕中間顯示計算結果
可以用麥克勞林公式計算,但是費時,因此可以建立一張表
; ax向子程序傳遞角度
showsin: jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
ag0 db '0',0 ; sin(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 ; bl保存除得的結果
mov bh,0
add bx,bx ; 因爲ag0等佔兩個字節(值爲標號ago的偏移地址)
; 因此bx要*2纔是ag0等相對於table偏移地址
mov bx,table[bx]
; 顯示sin(x)對應的字符串
mov si,160*12+40*2
shows: mov ah,cs:[bx]
cmp ah,0 ; 每個值的字符串長度不一樣,因此在末尾加0
je showret ; 方便讀取
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret: pop si
pop es
pop bx
ret
同時,最好在程序中加上對角度值是否超出範圍的檢測
通過依據數據,直接計算要找的元素的位置的表,稱爲直接定址表
16.4 程序入口地址的直接定址表
我們可以直接在定址表中存儲子程序的地址,方便實現不同子程序的調用
實現一個子程序setscreen,有如下功能:清屏,設置前景色,設置背景色,向上滾動一行
參數:ah傳遞功能號 0:清屏 1:設置前景色 2:設置背景色 3:向上滾動一行
al傳送顏色值 (al)屬於{0,1,2,3,4,5,6,7}
如何清屏:當前屏幕字符設置位空格符
向上滾動一行:第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 ; 第0、1、2位與前景色的有關
; 其他位和1與運算,保持不變
or es:[bx],al ;將前3位設置爲對應的顏色
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 ; 第4、5、6位與背景色的有關
; 其他位和1與運算,保持不變
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
; 將這些子程序入口存儲在一個表中
; 功能和*2=對應子程序在表中的偏移
setscreen: jmp short set
table dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3 ; 判斷功能號是否大於3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根據ah提供的功能號找到對應子程序在table中的偏移
call word ptr table[bx] ; 調用
sret: pop bx
ret
這種方法便於擴充,如果加入一個新的功能子程序,只需要在地址表中加入它的入口地址即可