Skip to content

直接定址表

描述单元长度的标号(数据标号)

普通代码标号(如 s:)只表示地址,不含类型信息。
数据标号(定义在 db/dw/dd 旁边,无冒号)同时描述地址和单元长度:

asm
assume cs:code

code segment
  a db 1,2,3,4,5,6,7,8    ; 数据标号 a,字节型
  b dw 0                   ; 数据标号 b,字型

  start:
    mov si, 0
    mov cx, 8
  s:
    mov al, a[si]           ; 访问 a 偏移 si 处的字节
    mov ah, 0
    add b, ax               ; b 是字单元,合法
    inc si
    loop s

    mov ax, 4c00h
    int 21h
code ends
end start

数据标号的等价写法:

asm
; 以下等价
mov ax, b         ↔   mov ax, cs:[8]
mov b, 2mov word ptr cs:[8], 2
inc b             ↔   inc word ptr cs:[8]

; 注意:类型不匹配会编译报错
mov al, b         ; 错误!b 是字单元,al 是 8 位寄存器

在其他段中使用数据标号

数据通常定义在 data 段而非 code 段。此时用 assumedata 段与 DS 关联,编译器才能正确生成访问指令:

asm
assume cs:code, ds:data     ; 关键: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              ; 运行时手动设置 DS(assume 不自动设置!)

    mov si, 0
    mov cx, 8
  s:
    mov al, a[si]           ; 编译为 mov al, ds:[0+si]
    mov ah, 0
    add b, ax               ; 编译为 add ds:[8], ax
    inc si
    loop s

    mov ax, 4c00h
    int 21h
code ends
end start

关键点: assume 只是告诉编译器"标号默认在哪个段寄存器下访问",不会自动为 DS/ES 赋值,程序员必须手动 mov ds, ax

将标号当数据使用

可以把标号本身(地址值)作为数据存储:

asm
data segment
  a db 1,2,3,4,5,6,7,8
  b dw 0
  c dw a, b              ; c 处存储 a 和 b 的偏移地址
  ; 等价于:c dw offset a, offset b
data ends

dd 版本(存段地址:偏移地址):

asm
data segment
  a db 1,2,3,4,5,6,7,8
  b dw 0
  c dd a, b       ; 每项 4 字节:低字=偏移地址,高字=段地址
  ; 等价于:c dw offset a, seg a, offset b, seg b
  ; seg 操作符:取标号所在段的段地址
data ends

直接定址表(查表法)

核心思想: 将计算结果预先存入表中,通过索引(数值)直接查表,避免复杂运算。

示例 1:以十六进制显示字节

将字节的高 4 位和低 4 位分别映射到 0~9, A~F 字符:

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

show:
    push bx
    push es
    push cx

    mov ah, al
    mov cl, 4
    shr ah, cl              ; ah = 高4位(0~15)
    and al, 00001111b       ; al = 低4位(0~15)

    mov bx, 0b800h
    mov es, bx

    ; 显示高位字符
    mov bl, ah
    mov bh, 0
    mov ah, table[bx]       ; 用高4位值查表
    mov es:[160*12+40*2], ah

    ; 显示低位字符
    mov bl, al
    mov bh, 0
    mov al, table[bx]       ; 用低4位值查表
    mov es:[160*12+40*2+2], al

    pop cx
    pop es
    pop bx
    ret

示例 2:查表显示 sin 值

将预先计算的 sin 字符串的偏移地址存入表中,通过角度值(/30 为索引)查表:

asm
showsin:
    jmp short show
    table dw ag0, ag30, ag60, ag90, ag120, ag150, ag180
    ag0   db '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 si
    push es

    ; 用角度/30 作为 table 的索引
    mov ah, 0
    mov bl, 30
    div bl              ; al = 角度 / 30(索引)
    mov bl, al
    mov bh, 0
    add bx, bx          ; bx*2(因为 table 存的是字型偏移地址)
    mov bx, table[bx]   ; 取字符串偏移地址

    ; 在屏幕中间显示字符串
    mov bx, 0b800h
    mov es, bx
    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 shows

showret:
    pop es
    pop si
    pop bx
    ret

为什么用直接定址表

  1. 算法清晰简洁: 查表比分支判断(if/else)更简洁
  2. 速度快: 一次内存访问 vs. 多次比较跳转
  3. 易于扩充: 只需修改表格内容,无需修改算法逻辑

类比 Rust:table[index]match 的查表替代版;还类似于函数指针表(vtable 的原型)。