前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【汇编语言】直接定址表(二)—— 「代码‘导航员’:直接定址表的功能与应用」

【汇编语言】直接定址表(二)—— 「代码‘导航员’:直接定址表的功能与应用」

作者头像
Crossoads
发布2025-02-07 11:26:16
发布2025-02-07 11:26:16
8300
代码可运行
举报
文章被收录于专栏:汇编语言汇编语言
运行总次数:0
代码可运行

前言

📌 汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。 本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。

1. 直接定址表

现在,我们将讨论用“查表”的方法编写相关程序的技巧。

1.1 问题引入(一)—— 为算法清晰而查表

任务:编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据。

1.2 分析与解决问题

1.2.1 两个十六进制表示一个字节

一个字节需要用两个十六进制数码来表示,所以,子程序需要在屏幕上显示两个ASCII 字符。

我们当然要用“0”、“1”、“2”、“3”、“4”、“5” 、“6” 、“7” 、“8” 、“9” 、“A”、“B”、“C”、“D”、“E”、“F”这16个字符来显示十六进制数码。我们可以将一个byte的高4位和低4 位分开,分别用它们的值得到对应的数码字符。比如 2Bh ,我们可以得到高4 位的值为2,低4 位的值为11。

1.2.2 得到数值对应的数码字符

那么我们如何用这两个数值得到对应的数码字符“2”和“B”呢?

1.2.2.1 最粗暴的方法——比较

我知道,我们一看就知道是 2和 B ,但CPU它不懂,它只懂 1 和 0。最简单的办法就是一个一个地比较,如下:

如果数值为 0,则显示“0”;

如果数值为 1,则显示“1”; :

如果数值为15,则显示“F”;

我们可以看出,这样做,程序中要使用多条比较、转移指令。程序将比较长,混乱。

1.2.2.2 使用映射关系来解决

显然,我们希望能够在数值0~15和字符“0 ~ F”之间找到一种映射关系。这样我们用0~15间的任何数值,都可以通过这种映射关系直接得到“0”~“F”中对应的字符。

(1)

数值0~9和字符“0”~“9”之间的映射关系是很明显的,即:

数值 + 30h = 对应字符的ASCII值

0+30h = “0”的ASCII值

1+30h = “1”的ASCII值

2+30h = “2”的ASCII值

(2)

但是,10~15和“A”~“F”之间的映射关系是:

数值+37h=对应字符的ASCII值:

10+37h=“A”的ASCII值

11+37h=“B”的ASCII值

12+37h=“C”的ASCII值

: :

1.2.2.3 存在的问题及解决

可见,我们是利用数值和字符之间的这种原本存在的映射关系,通过高 4 位和低4 位值得到对应的字符码。

但我们发现一个问题……由于映射关系的不同,我们在程序中必须进行一些比较,对于大于9的数值,我们要用不同的计算方法。

这样做,虽然使程序得到了简化。但是,如果我们希望用更简捷的算法,就要考虑用同一种映射关系从数值得到字符码。所以,我们就不能利用0~9和“0”~“9” 之间与 10~15 和 “A” ~“F ” 之间原有的映射关系。因为他们有两个映射关系,不满足我们的条件。

具体的做法是,我们建立一张表,表中依次存储字符“0”~“F”,我们可以通过数值0~15直接查找到对应的字符。

1.3 得到子程序

子程序如下:

代码语言:javascript
代码运行次数:0
复制
;用al传送要显示的数据

showbyte:
        jmp short show

        table db '0123456789ABCDEF'	;字符表

show:   push bx
        push es

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

        pop es
        pop bx
        ret

可以看出,在子程序中,我们在数值0~15和字符“0”~“F ” 之间建立的映射关系为:以数值N为table 表中的偏移,可以找到对应的字符。

1.4 反思

利用表,在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一集合中的对应数据。

这样做的目的一般来说有三个:

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

1.5 问题引入(二)—— 为加快运算速度而查表

在刚刚的子程序中,我们更多的是为了算法的清晰和简洁,而采用了查表的方法。下面我们来看一下,为了加快运算速度而采用查表的方法的情况。

任务:编写一个子程序,计算sin(x),x∈{0°,30°,60°,90°,120°,150°,180°},并在屏幕中间显示计算结果。

例如sin(30) 的结果显示为“0.5”。

1.6 分析与解决问题

1.7.1 使用麦克劳林公式

我们可以利用麦克劳林公式来计算sin(x)。 x 为角度,麦克劳林公式中需要代入弧度,则:

\sin x = \sin y\approx y-\frac{1}{3!} y^3+\frac{1}{5!}y^5
y=x/180*3.1415926

可以看出,计算sin(x)需要进行多次乘法和除法。乘除是非常费时的运算,它们的执行时间大约是加法、比较等指令的5倍。

1.7.2 还是用查表简单

那么我们如何才能够不做乘除而计算sin(x)呢?

我们看一下需要计算的sin(x)的结果:

我们可以看出,其实用不着计算,可以占用一些内存空间来换取运算的速度。

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

我们用 ax 向子程序传递角度。

1.7 得到子程序

子程序如下:

代码语言:javascript
代码运行次数:0
复制
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(0)对应的字符串“0.5”
        ag60     db '0.866',0		;sin(0)对应的字符串“0.866”
        ag90     db '1',0			;sin(0)对应的字符串“1”
        ag120    db '0.866',0		;sin(0)对应的字符串“0.866”
        ag150    db '0.5',0			;sin(0)对应的字符串“0.5”
        ag180    db '0',0			;sin(0)对应的字符串“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
        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 shows

showret:
        pop si
        pop es
        pop bx
        ret

在上面的子程序中,我们在角度值x和表示 sin(x) 的字符串集合table 之间建立的映射关系为:以角度值 /30 为table 表中的偏移,可以找到对应的字符串的首地址。

1.8 反思与总结

编程的时候要注意程序的容错性,即对于错误的输入要有处理能力。

在上面的子程序中,我们还应该在加上对提供的角度值是否超范围的检测。

如果提供的角度值不在合法的集合中,程序将定位不到正确的字符串,出现错误。

对于角度值的检测,大家请自行完成。

上面的两个子程序中,我们将通过给出的数据进行计算或比较而得到结果的问题,转化为用给出的数据作为查表的依据,通过查表得到结果的问题。

具体的查表方法 ,是用查表的依据数据 ,直接计算出所要查找的元素在表中的位置。像这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为:直接定址表

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

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

2.1 问题引入

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

  • (1)清屏。
  • (2)设置前景色。
  • (3)设置背景色。
  • (4)向上滚动一行

2.2 设置入口参数

那么入口参数如何设置呢?

入口参数说明:

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

2.3 各功能的实现

下面,我们讨论一下各种功能如何实现:

  • (1)清屏:将显存中当前屏幕中的字符设为空格符
  • (2)设置前景色:设置显存中当前屏幕中处于奇地址的属性字节的第0、1、2位;
  • (3)设置背景色:设置显存中当前屏幕中处于奇地址的属性字节的第4、5、6位;
  • (4)向上滚动一行:依次将第 n+1行的内容复制到第n行处:最后一行为空。

2.4 各功能的子程序

我们将这4 个功能分别写为 4 个子程序,请大家根据编程思想,自行读懂下面的程序。

代码语言:javascript
代码运行次数:0
复制
;功能子程序1:清屏
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 ;sub1 ends

;功能子程序2:设置前景色
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 ;sub2 ends

;功能子程序3:设置背景色
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 sub2s

	pop es
	pop cx
	pop bx
	ret ; sub3 ends

;功能子程序4:向上滚动一行
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
	rep movsb 			;复制
  	pop cx
	loop sub4s

	mov cx,80	
	mov si,0
	
sub4s1: 
	mov byte ptr es:[160*24+si],' '		;最后一行清空
	add si,2
	loop sub4s1

	pop ds
	pop es
	pop di
	pop si
	pop cx
	ret ;sub4 ends

2.5 使用直接定址表调用子程序

我们可以将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应。 对应的映射关系为:功能号*2=对应的功能子程序在地址表中的偏移。

程序如下:

代码语言:javascript
代码运行次数:0
复制
setscreen: jmp short set

    table  dw sub1,sub2,sub3,sub4

set:	
	push bx	
	cmp ah,3		;判断传递的是否大于 3
	ja sret
	mov bl,ah
	mov bh,0
	add bx,bx		;根据ah中的功能号计算对应子程序的地址在table表中的偏移
	
	call word ptr table[bx]	;调用对应的功能子程序

sret:	
	pop bx	
	iret

当然,我们也可以将子程序setscreen如下实现:

代码语言:javascript
代码运行次数:0
复制
setscreen:
	cmp ah,0
	je do1
	cmp ah,1
	je do2
	cmp ah,2
	je do3
	cmp ah,3
	je do4
	jmp short sret 

do1:	
	call sub1
	jmp short sret
	
do2:	
	call sub2
	jmp short sret
	
do3:	
	call sub3
	jmp short sret
	
do4:	
	call sub4

sret:	
	iret

2.6 总结

显然,用通过比较功能号进行转移的方法,程序结构比较混乱,不利于功能的扩充。比如说,在 setscreen 中再加入一个功能,则需要修改程序的逻辑,加入新的比较、转移指令。

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

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1. 直接定址表
    • 1.1 问题引入(一)—— 为算法清晰而查表
    • 1.2 分析与解决问题
      • 1.2.1 两个十六进制表示一个字节
      • 1.2.2 得到数值对应的数码字符
    • 1.3 得到子程序
    • 1.4 反思
    • 1.5 问题引入(二)—— 为加快运算速度而查表
    • 1.6 分析与解决问题
      • 1.7.1 使用麦克劳林公式
      • 1.7.2 还是用查表简单
    • 1.7 得到子程序
    • 1.8 反思与总结
  • 2. 程序入口地址的直接定址表
    • 2.1 问题引入
    • 2.2 设置入口参数
    • 2.3 各功能的实现
    • 2.4 各功能的子程序
    • 2.5 使用直接定址表调用子程序
    • 2.6 总结
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档