汇编:数据/代码查找表
数据查找表
汇编语言的数据查找表(Lookup Table, LUT) 是存储在内存中的连续数据集合,通过索引值直接定位数据地址,实现 “查表取值”(无需计算,直接读取),核心优势是快速高效,尤其适合映射关系固定的场景(如 ASCII 码转换、数学函数值、状态映射等)。
一、核心原理(底层视角)
- 存储结构:数据在内存中连续排列(按字节 / 字 / 双字对齐),每个元素占固定长度(如 1 字节 = 8 位、2 字节 = 16 位)。
- 寻址逻辑:目标数据地址 = 查找表基地址 + 索引值 × 单个元素长度(CPU 通过「基址 + 变址寻址」直接计算地址,无需循环遍历,1 条指令即可获取数据)
- 硬件关联:
- 表基地址通常存放在寄存器(如 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_TABLE或MOV BX, OFFSET JUMP_TABLE,将表的起始偏移地址加载到BX中,BX是 8086 约定的「基址寄存器」,专门用于存放基地址。 |
举例(对应之前的极简示例):
LEA BX, JUMP_TABLE ; 核心指令:把JUMP_TABLE的「偏移基地址」加载到BX
; 等价于 MOV BX, OFFSET JUMP_TABLE (OFFSET直接取汇编时确定的偏移值)
- 假设汇编后
JUMP_TABLE在CODE段的偏移地址是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 个字节。

浙公网安备 33010602011771号