【汇编语言】更灵活的定位内存地址的方法(三)—— 不同的寻址方式的灵活应用
文章目录
前言
📌
汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。
本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
1. 比较不同的寻址方式
如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式),就可以发现有以下几种方式:
(1)[iata] 用一个常量来表示地址,可用于直接定位一个内存单元;
(2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
(3)[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
(4)[bx+si]用两个变量表示地址;
(5)[bx+si+idata] 用两个变量和一个常量表示地址。
可以看到,从[idata]一直到[bx+si+idata],我们可以用更加灵活的方式来定位一个内存单元的地址。这使我们可以从更加结构化的角度来看待所要处理的数据。
下面我们将通过一系列问题来体会CPU提供多种寻址方式的用意,并学习一些相关的编程技巧。
2. 问题一
编程,将datasg段中每个单词的头一个字母改为大写字母。
assume cs:codesg,ds:datasg
datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends
codesg segment
start:……
codesg ends
end start
3. 问题一的分析与求解
3.1 分析
3.1.1 数据的存储结构
datasg 中的数据的存储结构,如下图所示。
我们可以看到,在datasg中定义了6个字符串,每个长度为16个字节(注意,为了直观,每个字符串的后面都加上了空格符,以使它们的长度刚好为16个字节)。
因为它们是连续存放的,可以将这6个字符串看成一个6行16列的二维数组。按照要求,需要修改每一个单词的第一个字母,即二维数组的每一行的第4列(相对于行首的偏移地址为3)。
3.1.2 分析处理过程
我们需要进行6次循环,用一个变量R定位行,用常量3定位列。处理的过程如下。
R=第一行的地址
mov cx,6;因为总共有六行
s: 改变第R行,第3列的字母为大写
R=下一行的地址
loop s
3.2 代码实现
我们用bx作变量,定位每行的起始地址,用3定位要修改的列,用[bx+idata]的方式来对目标单元进行寻址,程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6
s: mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s
4. 问题二
编程,将datasg段中每个单词改为大写字母。
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
codesg segment
start: ……
codesg ends
end start
5. 问题二的分析与求解
5.1 分析
5.1.1 数据的存储结构
datasg中数据的存储结构,如下图所示。
在datasg中定义了4个字符串,每个长度为16字节。(注意,为了使我们在Debug 中可以直观地查看,每个字符串的后面都加上了空格符,以使它们的长度刚好为16字节)。
因为它们是连续存放的,我们可以将这 4 个字符串看成一个 4行16列的二维数组。 按照要求,我们需要修改每一个单词,即二维数组的每一行的前3列。
5.1.2 分析处理过程
我们需要进行4x3次的二重循环(循环嵌套),用变量R 定位行,变量C定位列。
外层循环按行来进行;内层按列来进行。
我们首先用R定位第1行,然后循环修改R行的前3列;然后再用R定位到下一行,再次循环修改R行的前3列……, 如此重复直到所有的数据修改完毕。
处理的过程大致如下:
R=第一行的地址;
mov cx,4
s0: C=第一列的地址
mov cx,3
s: 改变R 行,C列的字母为大写
C=下一列的地址;
loop s
R=下一行的地址
loop s0
5.2 代码实现
我们用bx来作变量,定位每行的起始地址,用si定位要修改的列,用 [bx+si] 的方式来对目标单元进行寻址,程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:mov si,0
mov cx,3
s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
loop s0
6. 问题三
仔细阅读上面的程序,看看有什么问题?
思考后看分析。
7. 问题三的分析与求解
7.1 分析
问题在于cx的使用,我们进行二重循环,却只用了一个循环计数器,造成在进行内层的时候覆盖了外层循环的循环计数值。
多用一个计数器又不可能,因为loop指令默认cx为循环计数器。
怎么办呢?
我们应该在每次开始内层循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值。
我们可以用寄存器dx来临时保存cx中的数值。
7.2 代码实现
看看我们改进后的程序。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:mov dx,cx;将外层循环的cx值保存在 dx中
mov si,0
mov cx,3;cx设置为内层循环的次数
S:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,dx;用dx中存放的外层循环的计数值恢复cx
loop s0;外层循环的1oop指令将cx中的计数值减1
7.2.1 分析实现的代码
上面的程序用dx来暂时存放cx中的值;
如果在内层循环中,dx寄存器也被使用,该怎么办?
我们似乎可以使用别的寄存器,但是CPU中的寄存器数量毕竟是有限的,如8086CPU只有14个寄存器。
在上面的程序中,si、cx、ax、bx,显然不能用来暂存cx中的值,因为这些寄存器在循环中也要使用;cs、ip、ds也不能用,因为cs:ip时刻指向当前指令,ds指向datasg段;可用的就只有:dx、di、es、ss、sp、bp等6个寄存器了。
可是如果循环中的程序比较复杂,这些寄存器也都被使用的话,那么该如何?
我们在这里讨论的问题是,程序中经常需要进行数据的暂存,我们怎样做将更为合理。
这些数据可能是寄存器中的,也可能是内存中的。我们可以用寄存器暂存它们,但是这不是一个一般化的解决方案,因为寄存器的数量有限,每个程序中可使用的寄存器都不一样。我们希望寻找一个通用的方案,来解决这种在编程中经常会出现的问题。
显然,我们不能选择寄存器,那么可以使用的就是内存了。我们可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复。这样我们就需要开辟一段内存空间。
7.2.2 改进后的程序
一起来看再次改进的程序。
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
dw 0 ;定义一个字,用来暂存cx
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0: mov ds:[40H],cx;将外层循环的cx值保存在datasg:40H单元中
mov si,0
mov cx,3;cx设置为内存循环的次数
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ds:[40H];用datasg:40H单元中的值恢复cx
loop s0;外层循环的loop指令将cx中的计数值减 1
mov ax,4c00h
int 21h
codesg ends
end start
7.2.3 分析改进后的程序
改造后的程序中,用内存单元来保存数据,可是上面的作法却有些麻烦,因为如果需要保存多个数据的时候,你必须要记住数据放到了哪个单元中,这样程序容易混乱。
我们使用内存来暂存数据,这一点是确定了的,但是值得推敲的是,我们用怎样的结构来保存这些数据,而使得我们的程序更加清晰,更容易读懂和被接受?
一般来说,在需要暂存数据的时候,我们都应该使用栈,回忆一下,栈空间在内存中,采用相关的指令,如:push、pop等,可对其进行特殊的操作。
7.2.4 再次改进程序
下面,再次改进我们的程序。
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
stacksg segment;定义一个段,用来作栈段,容量为16个字节
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0: push cx;将外层循环的cx值压栈
mov si,0
mov cx,3;cx设置为内层循环的次数
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx;从栈顶弹出原cx的值,恢复cx
loop s0;外层循环的loop指令将cx中的计数值减 1
mov ax,4c00h
int 21h
codesg ends
end start
8. 问题四
编程,将datasg段中每个单词的前四个字母改为大写字母。
assume cs:codesg,ds:datasg,ss:stacksg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends
codesg segment
start: ……
codesg ends
end start
9. 问题四的分析与求解
9.1 分析
9.1.1 数据的存储结构
datasg中的数据的存储结构,如下图所示。
在 datasg 中定义了4个字符串,每个长度为16字节(注意,为了使我们在 Debug 中可以直观地查看,每个字符串的后面都加上了空格符,以使它们的长度刚好为16个字节)。
因为它们是连续存放的,我们可以将这4个字符串看成一个4行16列的二维数组,按照要求,我们需要修改每个单词的前4个字母,即二维数组的每一行的3~6列。
9.1.2 分析处理过程
我们需要进行4x4次的二重循环,用变量R定位行,常量3定位每行要修改的起始列,变量C定位相对于起始列的要修改的列。
其实这个题也就是将前边的三个题目给“总结”概括了一下!
外层循环按行来进行,内层按列来进行。我们首先用R定位第1行,循环修改R行的3+C( 0≤C≤3 )列; 然后再用R定位到下一行,再次循环修改R行的3+C(0≤C≤3)列……, 如此重复直到所有的数据修改完毕。
处理的过程大致如下:
R=第一行的地址;
mov cx,4
s0: C=第一个要修改的列相对于起始列的地址
mov cx,4
s: 改变R行,3+C列的字母为大写
C=下一个要修改的列相对于起始列的地址
loop s
R=下一行的地址
loop s0
我们用bx来作变量,定位每行的起始地址,用si定位要修改的列,用 [ bx+3+si ]的方式来对目标单元进行寻址。
请在实验中自己完成这个程序。
10. 总结
✍这一章中,我们主要讲解了更灵活的寻址方式的应用和一些编程方法,主要内容有:
- 寻址方式 [bx(或si、di)+idata]、 [bx+si(或di)]、 [bx+si(或di)+idata]的意义和应用。
- 二重循环问题的处理。
- 栈的应用。
- 大小写转化的方法。
- and 、or 指令。
下一章中,我们将对寻址方式的问题进行更深入地探讨。之所以如此重视这个问题,是因为寻址方式的适当应用,使我们可以以更合理的结构来看待所要处理的数据。而为所要处理的看似杂乱的数据设计一种清晰的数据结构是程序设计的一个关键的问题。
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!
原文地址:https://blog.csdn.net/2301_80191662/article/details/143827962
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!