Skip to content

Latest commit

 

History

History
541 lines (456 loc) · 13.2 KB

16-直接定址表.md

File metadata and controls

541 lines (456 loc) · 13.2 KB

直接定址表

描述了单元长度的标号

数据标号: 可以使用一种标号, 不但表示内存单元的地址, 还表示了内存单元的长度, 即表示在此标号处的单元是字节单元,字单元还是双字单元

以下程序将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,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后面没有:. 它们同时描述内存地址和单元长度(如 b dw 0).

以下指令中,标号b代表了一个内存单元, 地址为code:8, 长度为2个字节

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

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

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

mov al,b会引起编译错误, 因为b代表的内存单元是字单元, 而al是8位寄存器.

在其他段中使用数据标号

一般情况下不会在代码段中定义数据, 而是将数据定义到其他段中.

注意: 在后面加有:的地址标号, 只能在代码段中使用, 不能在其他段中使用

assume cs:code, ds:data   ;用ds寄存器和data段相连, code段中就可以使用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
       loop s

       mov ax,4c00h
       int 21h

code ends
end start

注意: 如果想在代码段中直接用数据标号访问数据, 则需要用伪指令assume将标号所在的段和一个段寄存器联系起来. 否则会编译出错.

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

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

这些实际编译出的指令, 都默认访问单元的段地址在ds中, 而实际要访问的段为data, 所以在这些指令执行之前, 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      ;数据标号c处存储2个双字节型数据为标号a的偏移地址和段地址. 标号b的偏移地址和段地址
                ;相当于 c dw offset a, seg a, offset b, seg b   ;seg操作符, 功能为取得某一标号的段地址
data ends

直接定址表

编程, 以16进制的形式在屏幕中间显示给定的字节型数据

分析:

  1. 一个字节需要用2个16进制的数码来表示. 所以子程序需要在屏幕上显示2个ASCII字符
  2. 可以将一个字节的高4位和低4位分开, 分别用它们的值得到对应的数码字符
    • 0~9: 数值+30h=对应字符的ASCII码; 10~15: 数值+37h=对应字符的ASCII码
    • 更简便的方法, 需要找到0~f0~15之间一致的映射关系. 可以建立一张表, 依次存储字符0~f, 可以通过数值0~15直接查找到对应的字符

子程序如下:

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

    show: push bx
          push es
          push cx

          mov ah,al
          mov cl,4
          shr ah,cl           ;右移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],ah

          pop cx
          pop es
          pop bx
          ret

在子程序中,数值015和字符0f向对应: 以数值N为table表中的偏移,可以找到对应的字符

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

编写一个子程序, 计算sin(x), x∈{0°,30°,60°,90°,120°,150°,180°}

分析:将所要计算的sin(x)的结果都存储到一张表中; 然后用角度值来查表, 找到对应的sin(x)的值

用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                                ;sin(30)对应的字符串"0.5"
 ag60   db '0.866',0                              ;sin(60)对应的字符串"0.866"
 ag90   db '1',0                                  ;sin(90)对应的字符串"1"
ag120   db '0.866',0                              ;sin(120)对应的字符串"0.866"
ag150   db '0.5',0                                ;sin(150)对应的字符串"0.5"
ag180   db '0',0                                  ;sin(180)对应的字符串"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
      mov bh,0
      add bx,bx           ;table中存放的是字型数据, 所以bx*2
      mov bx,table[bx]

      ;以下显示sin(x)对应的字符串

      mov si,160*12+40*2
shows: mov ah,cs:[bx]
       cmp ah,0
       je showret
       mov es:[si],ah
       inc bx
       add si,2
       jmp short shows

showret: pop si
         pop es
         pop bx
         ret

角度值/30=table中的偏移,可以找到对应的字符串的首地址

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

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

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

  1. 清屏
  2. 设置前景色
  3. 设置背景色
  4. 向上滚动一行 入口参数说明:
  5. 用ah寄存器传递功能号: 0表示清屏, 1表示设置前景色, 2表示设置背景色, 3表示向上滚动一行
  6. 对与1,2号功能, 用al传送颜色值. (al)∈{0,1,2,3,4,5,6,7}
    • 清屏: 将现场中低昂去屏幕中的字符设置为空格符
    • 前景色: 设置显存中当前屏幕处于奇地址的属性字节的第0,1,2位
    • 背景色: 设置显存中当前屏幕处于奇地址的属性字节的第4,5,6位
    • 向上滚动一行: 依次将第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
        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 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      ;共复制24行

sub4s: push cx
       mov cx,160     ;每行有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 cx
       ret

将这些功能子程序的入口地址存储在一个表中. 对应关系: 功能号*2=对应的功能子程序在地址表中的偏移

setscreen: jmp short set
     table dw sub1,sub2,sub3,sub4
      set: push bx
           cmp ah,3
           ja sret      ;大于3就退出
           mov bl,ah
           mov bh,0
           add bx,bx    ;根据ah中的功能号计算对应子程序在table表中的偏移
           call word prt table[bx]    ;调用对应的子程序
    sret:  pop bx
           ret

实验16 编写包含多个功能子程序的中断例程

安装一个新的int7ch中断例程,为显示输出提供如下功能:

  1. 清屏
  2. 设置前景色
  3. 设置背景色
  4. 向上滚动一行

入口参数说明:

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

分析:

  1. 复制代码到0:200
  2. 将int7ch入口地址指向0:200

安装程序:

assume cs:code

stack segment
  db 128 dup (0)
stack ends

code segment
  start: mov ax,stack
         mov ss,ax
         mov sp,128

         push cs
         pop ds

         mov ax,0
         mov es,ax

         mov si,offset setscreen                      ;设置ds:si指向源地址
         mov di,204h                                  ;设置es:di指向目标地址
         mov cx,offset setscreenend-offset setscreen  ;设置cx为传输长度
         cld                                          ;设置传输方向为正
         rep movsb

         push es:[7ch*4]
         pop es:[200h]
         push es:[7ch*4+2]
         pop es:[202h]      ;暂存BIOS原有的int7c中断例程地址

         cli
         mov word ptr es:[7ch*4],204h
         mov word ptr es:[7ch*4+2],0  ;设置新的int7c中断例程的地址0:204h
         sti    

         mov ax,4c00h
         int 21h

         org 204h        ;写给编译器执行的, 本行以后的程序会以204h为基础进行偏移计算
setscreen: jmp short set
     ;------------
     ;table 表中存储的的标号地址是在编译时计算的, 如果不做任何处理, 是以主程序的标号地址来计算的(sub1 即表示 sub1-start+0).
     ; 所以安装到中断例程中后, 会导地址指向错误. 因为在执行中断例程执行时(cs)=0,(ip)=204h. sub1真正应该表示的标号地址为sub1-setscreen+204h
     ;两种解决方案
     ;1. 手动计算偏移地址`table dw sub1-setscreen+204h,sub2,sub3,sub4`
     ;2. 在安装中断例程之前设置`org 204h`,通知编译器重新计算偏移
     table dw sub1,sub2,sub3,sub4
      set: push bx
           cmp ah,3
           ja sret      ;大于3就退出
           mov bl,ah
           mov bh,0
           add bx,bx    ;根据ah中的功能号计算对应子程序在table表中的偏移
           call word ptr table[bx]    ;调用对应的子程序
    sret:  pop bx
           iret  

;----------清屏-----------
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 cx
       push si
       push di
       push es
       push ds
       mov si,0b800h
       mov es,si
       mov ds,si
       mov si,160     ;ds:si指向第1行
       mov di,0       ;es:di指向第0行
       cld            ;设置传输方向为正
       mov cx,24      ;共复制24行

sub4s: push cx
       mov cx,160     ;每行有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

setscreenend: nop

code ends
end start

测试程序1(清屏):

assume cs:code
code segment
   mov ah,0
   int 7ch
   mov ax,4c00h
   int 21h
code ends
end

测试程序2(设置前景色):

assume cs:code
code segment
   mov ah,1
   mov al,1
   int 7ch
   mov ax,4c00h
   int 21h
code ends
end

测试程序3(设置背景色):

assume cs:code
code segment
   mov ah,2
   mov al,2
   int 7ch
   mov ax,4c00h
   int 21h
code ends
end

测试程序4(向上滚动一行):

assume cs:code
code segment
   mov ah,3
   int 7ch
   mov ax,4c00h
   int 21h
code ends
end