彙編語言(王爽)第十六章 直接定址表

第十六章

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

這種方法便於擴充,如果加入一個新的功能子程序,只需要在地址表中加入它的入口地址即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章