汇编语言(王爽)第十六章 直接定址表

第十六章

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

这种方法便于扩充,如果加入一个新的功能子程序,只需要在地址表中加入它的入口地址即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值