Skip to content

Latest commit

 

History

History
316 lines (230 loc) · 9.52 KB

File metadata and controls

316 lines (230 loc) · 9.52 KB

第31节 直接定址表

❤️💕💕汇编语言目前仍在发挥着不可替代的作用,在效率上无可替代,在底层,学习linux内核,计算机外围设备和驱动,都离不开汇编。Myblog:http://nsddd.top


[TOC]

直接定址表

直接定址表是使用查表得方式解决问题

这种方式在高级语言的数据结构很像的,比如说Java的哈希表

我们利用一个表,在两个数据集合之间建议一个映射关系,用查表的方法根据给出的数据得到在宁一个集合中的对应数据。

现在我们讨论用查表的方法编写相关程序的技巧。

编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据。

分析:一个字节需要用两个十六进制数码来表示,所以子程序需要在屏幕上显示两个ASCII字符。

分类

  • 数据的直接定址表
  • 代码的直接定址表

案例

我们当然要用"0"、"1"、"2"、"3"、"4"、"5"、"6"、"7"、"8"、"9"、"A"、"B"、"C"、"D"、"E"、"F"这16个字符来显示十六进制数码。

我们可以将一个字节的高4位和低4位分开,分别用它们的值得到对应的数码字符。比如2Bh,可以得到高4位的值为2,低4位的值为11,那么如何用这两个数值得到对应的数码字符“2”和“B”呢?

最简单的办法就是一个一个地比较,如下:

如果数值为0,则显示“0”;
如果数值为1,则显示“1”;
...
...
...
如果数值为11,则显示“B”;
...
...
...

我们可以看岀,这样做程序中要使用多条比较、转移指令。程序将比较长混乱。

显然我们希望能够在数值015和字符"0"-"F"之间找到一种映射关系。这样用015间的任何数值,都可以通过这种映射关系直接得到"0"-"F"中对应的字符。

数值0~9和字符"0"-"9"之间的映射关系是很明显的,即:

数值+30h=对应字符的ASCII值
0+30h="0" 的 ASCII 值
1+30h="1" 的 ASCII 值
2+30h="2" 的 ASCII 值
...
...
...

但是10~15和"A"-"F"之间的映射关系是:

数值+37h=对应字符的ASCII值
10+37h="A" 的 ASCII 值
11+37h="B" 的 ASCII 值
12+37h="C" 的 ASCII 值
...
...
...

可见我们可以利用数值和字符之间的这种原本存在的映射关系,通过高4位和低4位值得到对应的字符码。

但是由于映射关系的不同,我们在程序中必须进行一些比较,对于大于9的数值,我们要用不同的计算方法。

这样做,虽然使程序得到了简化。但是如果我们希望用更简捷的算法,就要考虑用同一种映射关系从数值得到字符码。所以,我们就不能利用0~9和"0"-"9"之间与10-15和"A"-"F"之间原有的映射关系。

因为数值0~15和字符"0"-"F"之间没有一致的映射关系存在,所以我们应该在它们之间建立新的映射关系。

具体的做法是,建立一张表,表中依次存储字符"0"~"F",我们可以通过数值0-15直接查找到对应的字符。

子程序如下。

;用al传送要显示的数据

showbyte:jmp short show
        table db '0123456789ABCDEF'     ;字符表  0 ~ 15

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
        mov ah,table[bx]        ;用高4位的值作为相对于table的偏移,取得对应的字符

        mov bx,0b800h
        mov es,bx
        mov es:[160*12+40*2],ah

        mov bl,al
        mov bh,0
        mov al,table[bx]        ;用低4位的值作为相对于table的偏移,取得对应的字符

        mov es:[160*12+40*2+2],al

        pop es
        pop bx
        ret

可以看出在子程序中,我们在数值0~15和字符"0"-"F"之间建立的映射关系为:以数值N为table表中的偏移,可以找到对应的字符。

利用表在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一集合中的对应数据。这样做的目的一般来说有以下3个。

(1)为了算法的清晰和简洁; (2)为了加快运算速度; (3)为了使程序易于扩充。

在上面的子程序中,我们更多的是为了算法的清晰和简洁,而釆用了查表的方法。下面我们来看一下,为了加快运算速度而采用查表的方法的情况。

  1. 编程的时候要注意程序的容错性,即对于错误的输入要有处理能力。在上面的子程序中,我们还应该再加上对提供的角度值是否超范围的检测。
  2. 如果提供的角度值不在合法的集合中,程序将定位不到正确的字符串,出现错误。对于角度值的检测,请读者自行完成。
  3. 我们将通过给出的数据进行计算或比较而得到结果的问题,转化为用给出的数据作为查表的依据,通过查表得到结果的问题。
  4. 具体的查表方法,是用查表的依据数据,直接计算出所要查找的元素在表中的位置。像这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为直接定址表。

代码的直接定址表

我们可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。我们看下面的问题。

实现一个子程序setscreen,为显示输出提供如下功能。

(1)清屏; (2)设置前景色; (3)设置背景色; (4)向上滚动一行。

入口参数说明如下。

(1)用ah寄存器传递功能号:0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行; (2)对于1、2号功能,用al传送颜色值,(al)∈{0,1,2,3,4,5,6,7}。

下面我们讨论一下各种功能如何实现。

(1)清屏:将显存中当前屏幕中的字符设为空格符; (2)设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位; (3)设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位; (4)向上滚动一行:依次将第n+1行的内容复制到第n行处;最后一行为空。

我们将这4个功能分别写为4个子程序,请读者根据编程思想,自行读懂下面的程序。

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 ex
        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       ;共复制24行

sub4s:  push ex
        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

当然,我们也可以将子程序setscreen如下实现。

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

显然用通过比较功能号进行转移的方法,程序结构比较混乱,不利于功能的扩充。比如说,在setscreen中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。

用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。

END 链接