• 汇编语言-17直接定址表


    单元长度的标号

    在代码段中使用标号来标记指令、数据、段的起始地址。比如,下面的程序将code段中的a标号处的8个数据累加,结果存储到b标号处的字中。

    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
        loops
        
        mov ax,4c00h
        int 21h
        
    code ends
    end start
    

    程序中, code、a、b、start、s都是标号。这些标号仅仅表示了内存单元的地址。

    还可以使用一种标号,这种标号不但表示内存单元的地址,还表示了内存单元的长度,即表示在此标号处的单元,是一个字节单元,还是字单元,还是双字单元。上面的程序还可以写成这样:

    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
    

    在code段中使用的标号a 、b后面没有":",它们是同时描述内存地址和单元长度的标号。标号a,描述了地址code:0, 和从这个地址开始,以后的内存单元都是字节单元;而标号b 描述了地址code:8, 和从这个地址开始,以后的内存单元都是字单元。

    因为这种标号包含了对单元长度的描述,所以在指令中,它可以代表一个段中的内存单元。

    比如,对于程序中的"b dw 0":

    指令: mov ax,b
    相当于: mov ax,cs:[8]

    指令: mov b,2
    相当于: mov word ptr cs:[8],2

    指令: inc b
    相当于: inc word ptr cs:[8]

    在这些指令中,标号b代表了一个内存单元,地址为code:8, 长度为两个字节。

    下面的指令会引起编译错误:
    mov al,b
    因为b代表的内存单元是字单元,而al是8 位寄存器。

    对于程序中的"a db 1,2,3,4,5,6,7,8" :

    指令: mov al,a[si]
    相当于: mov al,cs:0[si]

    指令: mov al,a[3]
    相当于: mov al,cs:0[3]

    指令: mov al,a[bx+si+3]
    相当于: mov al,cs:0[bx+si+3]

    可见,使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。以后,我们将这种标号称为数据标号,它标记了存储数据的单元的地址和长度。它不同于仅仅表示地址的地址标号。

    在其他段中使用数据标号

    一般来说,我们不在代码段中定义数据,而是将数据定义到其他段中。在其他段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。
    注意,在后面加有":"的地址标号,只能在代码段中使用,不能在其他段中使用。下面的程序将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
            int 21h
    code ends
    end start
    

    注意,如果想在代码段中直接用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。当然,这种联系是编译器需要的,但绝对不是说,我们因为编译器的工作需要,用assume指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。我们在程序中还要使用指令对段寄存器进行设置。

    比如,在上面的程序中,我们要在代码段code中用data段中的数据标号a 、b访问数据,则必须用assume将一个寄存器和data段相联。在程序中,我们用ds寄存器和data段相联,则编译器对相关指令的编译如下。

    指令: mov al,a[si]
    编译为: mov al,[ si+0]

    指令: add b,ax
    编译为: add [8],ax

    因为这些实际编译出的指令,都默认所访问单元的段地址在ds中,而实际要访问的段为data, 所以若要访问正确,在这些指令执行前, ds中必须为data 段的段地址。则我们在程序中使用指令:

    mov ax,data
    mov ds,ax
    

    设置ds指向data段。

    可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。

    data segment
        a db 1,2,3,4,5,6,7,8
        b dw 0
        c dw a,b
    data ends
    

    数据标号c处存储的两个字型数据为标号a 、b 的偏移地址。相当于:

    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
    

    数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b的偏移地址和段地址。相当于:

    data segment
        a db 1,2,3,4,S,6,7,8
        b dw 0
        c dw offset a, seg a, offset b,seg b
    data ends
    

    seg操作符,功能为取得某一标号的段地址。

    直接定址表

    用查表的方法编写相关程序的技巧。

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

    分析: 一个字节需要用两个十六进制数码来表示,所以,子程序需要在屏幕上显示两个ASCII 字符。我们当然要用“0" 、“1 "、“2" 、“3" 、“4" 、“5" 、“6" 、“ 7” 、“8" 、“9" 、“A" 、“B" 、“C" 、“D" 、“E" 、“F” 这16个字符来显示十六进制数码。

    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
            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 和字符“O" ~ "F" 之间建立的映射关系为:以数值N为table表中的偏移,可以找到对应的字符。利用表,在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一集合中的对应数据。这样做的目的一般来说有以下3个:
    (1) 为了算法的清晰和简洁;
    (2) 为了加快运算速度;
    (3) 为了使程序易于扩充。

    程序入口地址的直接定址表

    在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。

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

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

    入口参数说明如下:

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

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

    将这4个功能分别写为4个子程序

    subl: push bx
        push cx
        push es
        mov bx ,0b800h
        mov es,bx
        mov bx,0
        mov cx,2000
    subls: mov byte ptr es:[bx],' '
        add bx,2
        loop subls
        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 ex
        pop bx
        ret
    
    sub4: push cx
        push si
        push di
        push es
        push ds
        
        mov si,0bB00h
        mov es,si
        rnov ds,si
        mov si,160   ;ds:si 指向第n+l行
        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 subl,sub2,sub3,sub4
            
        set: push bx
        
            cmp ah,3    ;判断功能号是否大于3
            ja sret
            mov bl,ah
            mov bh,O
            add bx,bx    ;根据ah中的功能号计算对应子程序在table 表中的偏移
            
    		call word ptr table[bx]    ;调用对应的功能子程序
        
        sret: pop bx
    		ret
    

    也可以将子程序setscreen如下实现:

    setscreen: cmp ah,O
            je dol
            cmp ah,1
            je do2
            cmp ah,2
            je do3
            cmp ah,3
            je do4
            jmp short sret
        
        dol: call subl
            jmp short sret
        do2: call sub2
            jmp short sret
        do3: call sub3
            jmp short sret
        do4: call sub4
    
    sret: ret
    

    用通过比较功能号进行转移的方法,程序结构比较混乱,不利于功能的扩充。比如说,在setscreen 中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。
    用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。

    如果这篇文章对你有用,麻烦关注一下本人微信公众号,关注送福利哦~
    微信公众号二维码
    不定期安利各种插件,编程技巧,编程思想,欢迎交流~
  • 相关阅读:
    Eclipse 中怎样自动格式化代码?
    如何使用Jfreechart生成柱状图?
    ADT OOP
    5.2 Construction for Reuse
    Lab6实验的一些思考
    软件构造的八个多维视图
    软件构造笔记5.1 Metrics, Morphology and External Observations of Reusability
    事后诸葛亮之感谢
    (Model)针对编译错误的友好提示
    第二次结对作业
  • 原文地址:https://www.cnblogs.com/aeolian/p/13854211.html
Copyright © 2020-2023  润新知