8086汇编学习笔记19

直接定址表

系列文章

描述单元长度的标号

之前一直在代码段中使用标号来标记指令、数据、段的起始地址。例如:

codes segment
s1: db 1, 2, 3, 4, 5, 6, 7, 8
s2: dw 8 dup (0)
start:
; ...codes...
strdup:
; ...codes...

以上的 s1s2startstrdup 都是标号,这些标号仅仅表示了内存单元的地址。

还可以使用一种标号,这种标号不但表示内存单元的地址,还表示了内存单元的长度,即表示在此标号处的单元,是一个字节单元、字单元还是双字单元。这种标号的格式中标号名后面没有冒号“ : ”,例如:

codes segment
    s1 db 1, 2, 3, 4, 5, 6, 7, 8
    s2 dw 8 dup (0)

这里的标号 s1s2 ,是同时描述内存地址和单元长度的标号:

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

对该标号地址的引用:

mov s2, 2

可以直接包含对单元长度的描述,相当于指令“ mov word ptr cs: [8], 2 ”。

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

在其它段中使用数据标号

一般不在代码段中定义数据,而将数据定义放到其它段中。在其它段中也可以使用数据标号来描述存储数据的单元的地址和长度。

注意,在后面带有“ : ”的地址标号,只能在代码段中使用,不能在其它段中使用。

以下程序将 datas 段中中 items 标号处的8个数据累加,结果存到 total 标号处的字中:

assume cs: codes, ds: datas
datas segment
    items db 10, 4, 6, 2, 21, 3, 7, 8
    total dw 0
datas ends

如果在代码段中直接用数据标号访问数据,则需要用伪指令 assume 将标号所在的段和一个寄存器联系起来,以此确定标号的段地址在哪一个段寄存器内。在实际代码中,只需要将段地址移到对应的寄存器上即可。

在代码段中,可以使用数据标号直接访问数据:

codes segment
start:
    mov ax, datas
    mov ds, ax
    mov si, 0
    mov cx, 8
sum:
    mov al, items[si]
    mov ah, 0
    add total, ax
    inc si
    loop sum
    mov ax, 4c00h
    int 21h
codes ends
end start

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

s1  db 1, 2, 3, 4
s2  dw 8 dup (0)
s3  dw s1, s2

如果将标号当做数据,那么如果它被存储在字型数据中,实际上存储的是标号的偏移地址;如果被存储在双字型数据中,它实际上存储的是标号的偏移地址和段地址。低地址字存储偏移地址,高地址字存储段地址。

类似于 offset 操作符,可以使用 seg 操作符来取某一数据标号或地址标号的段地址。

直接定址表

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

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

以下使用查表的方式,计算部分特殊角的三角函数正弦值。程序占用一部分内存空间来换取运算速度,将所要计算的结果存储在一张表中:

sin:jmp short _sin
    sin_table dw sin0, sin30, sin60, sin90, sin120, sin150, sin180
    sin0      db '0', '$'
    sin30     db '0.5', '$'
    sin60     db '0.866', '$'
    sin90     db '1', '$'
    sin120    db '0.866', '$'
    sin150    db '0.5', '$'
    sin180    db '0', '$'

以下利用角度值除以30来作为相对 sin_table 的偏移,通过查表的方式取对应字符串的偏移地址,放在 BX 寄存器中:

; param angle should be stored in AX
; return value is stored in DS:[BX]
_sin:
    push ax
    mov ah, 0
    mov bl, 30
    div bl
    mov bl, al
    mov bh, 0
    add bx, bx
    mov bx, sin_table[bx]
    pop ax
    ret

类似于以上这种可以通过依据数据,直接计算出所要找的元素的位置的表,称为直接定址表

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

例如,如果有以下子程序:

sub1:
    ; ...code...
sub2:
    ; ...code...
sub3:
    ; ...code...

可以将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应,可以直接通过查表的方式来选择对应功能:

case dw sub1, sub2, sub3
; ...
call word ptr case[bx]

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

使用BIOS进行磁盘读写

以3.5英寸软盘为例:

3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道又分为80个扇区,每个扇区的大小为512个字节。故3.5英寸磁盘的存储大小为1440KB,或大约1.4MB。

磁盘的实际访问由磁盘控制器进行,可以通过控制磁盘控制器来访问磁盘。只能以扇区为单位对磁盘进行读写,在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从0开始,而扇区号从1开始。

如果通过直接控制磁盘控制器来访问磁盘,需要涉及许多硬件细节。BIOS提供了 INT 13H 中断例程对磁盘进行访问,其入口参数为:

其返回值为:

同样可以使用 INT 13H 中断例程将内存中的数据写入磁盘。其入口参数为:

其返回值为:

注意,在使用 INT 13H 中断例程时要注意驱动器号是否正确,不要随意对硬盘中的扇区进行写入。

以下程序将当前屏幕上的内容保存在磁盘上:

assume cs: codes
codes segment
start:
    mov ax, 0b800h
    mov es, ax
    mov bx, 0
    mov al, 8
    mov ch, 0
    mov cl, 1
    mov dl, 0
    mov dh, 0
    mov ah, 3
    int 13h
    mov ax, 4c00h
    int 21h
codes ends
end start

1屏的内容占4000个字节,需要8个扇区,因此用0面0道的1~8扇区存储显存中的内容。