汇编:数据/代码查找表

数据查找表

汇编语言的数据查找表(Lookup Table, LUT) 是存储在内存中的连续数据集合,通过索引值直接定位数据地址,实现 “查表取值”(无需计算,直接读取),核心优势是快速高效,尤其适合映射关系固定的场景(如 ASCII 码转换、数学函数值、状态映射等)。

一、核心原理(底层视角)

  1. 存储结构:数据在内存中连续排列(按字节 / 字 / 双字对齐),每个元素占固定长度(如 1 字节 = 8 位、2 字节 = 16 位)。
  2. 寻址逻辑:目标数据地址 = 查找表基地址 + 索引值 × 单个元素长度(CPU 通过「基址 + 变址寻址」直接计算地址,无需循环遍历,1 条指令即可获取数据)
  3. 硬件关联
    • 表基地址通常存放在寄存器(如 x86 的 BX、SI,ARM 的 R1、R2),索引值来自寄存器或立即数。
    • 底层依赖 CPU 的「地址加法器」快速计算偏移量,比计算(如公式运算)节省时钟周期。

二、8086 汇编(16 位实模式)实战示例

以「ASCII 码转 BCD 码」为例(0-9 的 ASCII 码为 30H-39H,对应 BCD 码 0-9),用查表法实现转换,代码可直接在 DosBox+MASM 中运行:

; 数据段:定义查找表(ASCII码→BCD码映射,索引0-9对应ASCII 30H-39H)
DATA SEGMENT
    ASCII_TABLE DB 30H,31H,32H,33H,34H,35H,36H,37H,38H,39H  ; 索引0→'0'(30H), 索引1→'1'(31H)...
    BCD_VAL     DB 5        ; 待转换的BCD码(5 → 目标ASCII是35H)
    RESULT      DB ?        ; 存储结果(最终会存35H)
DATA ENDS

; 代码段:查表核心逻辑
CODE SEGMENT
    ASSUME CS:CODE, DS:DATA  ; 关联段寄存器
START:
    MOV AX, DATA             ; 1. 初始化数据段寄存器DS(x86不允许直接MOV DS, 立即数)
    MOV DS, AX               ;    底层:DS存放数据段基址,CPU通过DS×16+偏移量定位内存
    
    MOV AL, BCD_VAL          ; 2. 取索引值(BCD_VAL=5,AL=05H)
    LEA BX, ASCII_TABLE      ; 3. 取查找表基地址(BX = ASCII_TABLE的偏移地址,底层用LEA计算有效地址)
    MOV AH, 0                ; 4. 清零AH(避免AX高8位干扰,因AL是8位索引,BX+AL为16位地址)
    
    ; 核心查表指令:基址+变址寻址(BX=表基址,AL=索引,DS:BX+AL → 目标数据地址)
    MOV AL, [BX+AL]          ; 5. 查表取值:AL = ASCII_TABLE[5] = 35H(1条指令完成,底层地址加法器计算 BX+AL)
    
    MOV RESULT, AL           ; 6. 存储结果(RESULT=35H,即'5'的ASCII码)
    
    ; 程序退出(DOS中断21H,功能4CH)
    MOV AH, 4CH
    INT 21H
CODE ENDS
END START

三、sin(x)查表实例

assume cs:code
code segment
start:
    mov al,60       ; 要查找的角度(0/30/60/90/120/150/180)
    call showsin    ; 调用显示正弦值的子程序
    mov ax,4c00h    ; DOS中断:程序退出
    int 21h

; 子程序:显示对应角度的正弦值字符串
; 入口参数:al = 角度(仅支持0/30/60/90/120/150/180)
; 出口参数:无
showsin:
    jmp short show  ; 跳过查表数据区

; 字符串偏移地址表(每个表项2字节,对应各角度字符串的偏移)
table: dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
ag0:    db '0',0         ; 0°正弦值
ag30:   db '0.5',0       ; 30°正弦值
ag60:   db '0.866',0     ; 60°正弦值
ag90:   db '1',0         ; 90°正弦值
ag120:  db '0.866',0     ; 120°正弦值
ag150:  db '0.5',0       ; 150°正弦值
ag180:  db '0',0         ; 180°正弦值

show:
    ; 保护寄存器(避免破坏主程序的寄存器值)
    push bx
    push es
    push si

    ; 初始化显存段寄存器(0B800H是彩色文本模式显存段地址)
    mov bx,0B800H
    mov es,bx       ; 修正:原代码是mov ex,bx(错误寄存器名)

    ; 查表逻辑:角度 ÷ 30 = 表项索引(如60÷30=2,对应table[2])
    mov ah,0        ; ax = al(角度值,字节扩展为字)
    mov bl,30       
    div bl          ; 字节除法:ax ÷ bl → al=商(索引),ah=余数
    mov bl,al       
    mov bh,0        ; bx = 索引(0~6)
    add bx,bx       ; 索引×2(因为table每个项是2字节的偏移地址)
    mov bx,table[bx]; bx = 对应角度字符串的偏移地址

    ; 设置显示位置:屏幕第12行、第40列(显存偏移=160*行 + 2*列)
    mov si,160*12 + 40*2

shows:
    ; 读取字符串字符(代码段寻址,因为字符串在code段)
    mov ah,cs:[bx]
    cmp ah,0        ; 判断是否到字符串结束符(0)
    je showret      ; 结束符则跳转到返回

    ; 显示字符到显存(显存格式:低字节=ASCII,高字节=属性,这里默认属性0)
    mov es:[si],ah  ; 写入字符
    inc bx          ; 下一个字符
    add si,2        ; 下一个显存位置(+2是因为每个字符占2字节)
    jmp shows       ; 循环显示

showret:
    ; 恢复寄存器
    pop si
    pop es
    pop bx
    ret             ; 子程序返回

code ends
end start

代码查找表(示例一)

与数据查找表的原理类似,先从代码开始:

; 8086 极简跳转表示例
; 段定义:代码段包含跳转表和所有功能逻辑
CODE SEGMENT
    ASSUME CS:CODE  ; 代码段寄存器CS指向本代码段

; --------------------------
; 1. 定义代码地址查找表(跳转表)
; 表元素:各功能的入口地址(16位偏移,8086同段跳转)
; --------------------------
JUMP_TABLE DW FUNC0, FUNC1, FUNC2  ; 索引0→FUNC0,索引1→FUNC1,索引2→FUNC2

; --------------------------
; 2. 程序入口
; --------------------------
START:
    ; 模拟输入:AL = 1(要跳转到FUNC1)
    ; 可修改AL为0/1/2测试不同分支
    MOV AL, 1  

    ; --------------------------
    ; 核心:计算查表偏移并跳转
    ; --------------------------
    CBW             ; 将8位AL扩展为16位AX(索引转16位,避免溢出)
    SHL AX, 1       ; 索引×2(因为表元素是DW,占2字节)
    LEA BX, JUMP_TABLE  ; BX = 跳转表基地址(偏移)
    ADD BX, AX      ; BX = 表中目标地址的偏移(基地址+索引偏移)
    JMP [BX]        ; 间接跳转:跳转到CS:[BX]指向的地址

; --------------------------
; 3. 各功能实现(极简模拟)
; --------------------------
FUNC0:  ; 处理索引0
    MOV DL, '0'     ; 模拟显示"0":DL存字符ASCII
    JMP EXIT        ; 跳转到退出

FUNC1:  ; 处理索引1
    MOV DL, '1'     ; 模拟显示"1"
    JMP EXIT

FUNC2:  ; 处理索引2
    MOV DL, '2'     ; 模拟显示"2"
    JMP EXIT

; --------------------------
; 4. 程序退出
; --------------------------
EXIT:
    ; (可选)实际显示DL中的字符(DOS中断)
    MOV AH, 02H     ; DOS功能号:显示DL中的字符
    INT 21H         ; 执行中断,屏幕输出DL中的字符
    
    MOV AH, 4CH     ; DOS功能号:程序退出
    INT 21H         ; 调用中断,返回DOS

CODE ENDS
END START  ; 程序结束,入口为START

关键知识点:

一、先明确核心概念

在 8086 的分段寻址中,JUMP_TABLE物理地址 = CS×16 + 表的偏移地址(因为跳转表定义在CODE段,段地址由CS寄存器保存):

  • 表的基地址(起始地址):分为「段地址」和「偏移地址」两部分;
  • 索引偏移:由索引计算得到,最终叠加到基地址上,定位到表中目标元素。

二、JUMP_TABLE基地址的存储位置

地址部分存储载体具体说明
段地址CS寄存器因为JUMP_TABLE定义在CODE段(ASSUME CS:CODE),8086 执行代码时CS固定指向代码段的段地址,无需手动赋值(汇编器自动关联)。
偏移地址(基)通常存在BX寄存器我们通过LEA BX, JUMP_TABLEMOV BX, OFFSET JUMP_TABLE,将表的起始偏移地址加载到BX中,BX是 8086 约定的「基址寄存器」,专门用于存放基地址。
举例(对应之前的极简示例):
LEA BX, JUMP_TABLE  ; 核心指令:把JUMP_TABLE的「偏移基地址」加载到BX
; 等价于 MOV BX, OFFSET JUMP_TABLE (OFFSET直接取汇编时确定的偏移值)
  • 假设汇编后JUMP_TABLECODE段的偏移地址是0010H,则执行该指令后BX = 0010H(基偏移);
  • 此时JUMP_TABLE的基物理地址 = CS×16 + BX(比如CS=0B00H,则物理地址 = 0B000H+0010H=0B010H)。

三、索引对应的「偏移地址」的计算与存储

表中目标元素的偏移 = 「表的基偏移(BX)」 + 「索引 × 元素长度」,这个最终偏移的计算 / 存储分两步:

1. 索引计算的中间值(临时存储)
计算步骤存储载体具体说明
原始索引(如 1)AL初始索引存在AL(8 位),比如示例中MOV AL, 1
扩展为 16 位索引AX通过CBW指令将AL(8 位)扩展为AX(16 位),避免 8 位转 16 位溢出。
索引 ×2(元素长度)AX通过SHL AX, 1(左移 1 位等价 ×2),计算出索引对应的字节偏移(因为表元素是DW,占 2 字节),此时AX中是「纯索引偏移值」(比如索引 1→AX=2)。
2. 最终目标偏移(定位到表中元素)
最终偏移存储载体具体说明
表中目标元素的偏移BX通过ADD BX, AX,将「表的基偏移(BX)」和「索引偏移(AX)」叠加,最终BX中是表中目标元素的偏移地址(比如基偏移 0010H + 索引偏移 2 → BX=0012H)。

四、LEA用法

LEA(Load Effective Address)是 8086 汇编中一个非常实用且重要的指令,用于计算内存地址的偏移量,并将其加载到指定的寄存器中,而不进行实际的内存访问。

一、基本语法与功能
LEA destination, source
  • destination:必须是 16 位的通用寄存器(AX, BX, CX, DX, SI, DI, BP, SP)

  • source:必须是内存操作数(包含方括号 []

  • 功能:计算源操作数的有效地址(偏移地址),将结果存入目标寄存器

二、与 MOV 指令的关键区别
; 假设 DS=1000h, SI=200h, 地址 10200h 处的值为 1234h

LEA AX, [SI]      ; AX = 0200h(SI 的值)
MOV AX, [SI]      ; AX = 1234h(内存 10200h 处的值)

LEA BX, [SI+10h]  ; BX = 0210h
MOV BX, [SI+10h]  ; BX = 内存 10210h 处的值

核心区别LEA计算地址但不访问内存;MOV访问内存获取数据。


代码查找表(示例二)

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

1、清屏

2、设置前景色

3、设置背景色

4、向上滚动一行

assume cs:code

code segment
start:
    ; 测试示例:可修改ah/al测试不同功能
    ; ah=0:清屏  ah=1:设置前景色  ah=2:设置背景色  ah=3:向上滚动一行
    ; al=颜色值(0-7,仅ah=1/2时有效)
    mov ah,0       ; 选择功能:清屏
    ; mov ah,1     ; 选择功能:设置前景色
    ; mov al,2     ; 前景色设为绿色(0=黑,1=蓝,2=绿,3=青,4=红,5=洋红,6=棕,7=白)
    ; mov ah,2     ; 选择功能:设置背景色
    ; mov al,1     ; 背景色设为蓝色
    ; mov ah,3     ; 选择功能:向上滚动一行
    call setscreen

    mov ax,4c00h   ; 程序退出
    int 21h

; 子程序:setscreen
; 功能:根据ah的值调用对应子功能
; 参数:ah=功能号(0-3),al=颜色值(仅ah=1/2时有效)
setscreen:
    jmp short set
    table dw sub1,sub2,sub3,sub4  ; 功能跳转表(0→sub1,1→sub2,2→sub3,3→sub4)
set:
    push bx
    cmp ah,3       ; 功能号超过3则直接返回
    ja sret
    mov bl,ah
    mov bh,0
    add bx,bx      ; 每个地址占2字节,bx=ah*2
    call word ptr table[bx]  ; 调用对应子功能
sret:
    pop bx
    ret

; 子功能1:清屏(将显存中所有字符设为空格)
sub1:
    push bx
    push cx
    push es
    mov bx,0B800H
    mov es,bx      ; 显存段地址
    mov bx,0       ; 偏移地址从0开始
    mov cx,2000    ; 80列×25行=2000个字符
sub1s:
    mov byte ptr es:[bx],' '  ; 字符设为空格
    add bx,2        ; 下一个字符(+2跳过属性字节)
    loop sub1s
    pop es
    pop cx
    pop bx
    ret

; 子功能2:设置前景色(al=前景色值0-7)
sub2:
    push bx
    push cx
    push es
    mov bx,0B800H
    mov es,bx
    mov bx,1       ; 从属性字节开始(偏移1)
    mov cx,2000    ; 遍历所有字符的属性字节
sub2s:
    ; 保留属性字节其他位,仅修改前景色(位2-0)
    and byte ptr es:[bx],11111000B  ; 清空前景色位
    or es:[bx],al                   ; 设置新前景色
    add bx,2                        ; 下一个属性字节
    loop sub2s
    pop es
    pop cx
    pop bx
    ret

; 子功能3:设置背景色(al=背景色值0-7)
sub3:
    push bx
    push cx
    push es
    mov cl,4
    shl al,cl      ; 背景色移到属性字节的位6-4(al<<4)
    mov bx,0B800H
    mov es,bx
    mov bx,1       ; 从属性字节开始
    mov cx,2000
sub3s:
    ; 保留属性字节其他位,仅修改背景色(位6-4)
    and byte ptr es:[bx],10001111B  ; 清空背景色位
    or es:[bx],al                   ; 设置新背景色
    add bx,2                        ; 下一个属性字节
    loop sub3s
    pop es
    pop cx
    pop bx
    ret

; 子功能4:向上滚动一行(将第2-25行上移,最后一行清空格)
sub4:
    push cx
    push si
    push di
    push es
    push ds
    mov si,0B800H
    mov es,si      ; 目标段:显存
    mov ds,si      ; 源段:显存
    mov si,160     ; 源偏移:第2行起始(80×2=160字节)
    mov di,0       ; 目标偏移:第1行起始
    cld            ; 正向传输
    mov cx,24      ; 复制24行(第2-25行→第1-24行)
sub4s:
    push cx
    mov cx,160     ; 每行160字节(80字符×2)
    rep movsb      ; 复制一行
    pop cx
    loop sub4s
    ; 清空最后一行(第25行)
    mov cx,80      ; 80个字符
    mov si,0       ; 偏移从0开始
sub4s1:
    mov byte ptr es:[160*24+si],' '  ; 第25行起始地址=160×24
    add si,2
    loop sub4s1
    pop ds
    pop es
    pop di
    pop si
    pop cx
    ret

code ends
end start

关键知识点:

一、惯例和效率

  • ES 通常用作"附加段",适合显存这样的特殊区域

  • DI 是专门设计用于目标索引的寄存器

二、最小编址单位

x64 架构和其前身 x86 一样,内存的最小编址单位是 1 个字节。

posted @ 2025-12-09 16:03  chenlight  阅读(3)  评论(0)    收藏  举报  来源