前言
点赞再看,养成习惯!
该系列博文基于王爽老师 <汇编语言 第四版> 一书,需要的同学链接自取:
链接:https://pan.baidu.com/s/1NAgD1Z15LtK1BuH92xmICA
提取码:xlzb
另外书中提到的DosBox软件不想去官网下载的小伙伴也可以自取:
链接:https://pan.baidu.com/s/1O6PnLb_hN-WUS2avicNpcw
提取码:xlzb
最后如果还没有计算机基础的同学,建议先补充下计算机相关的基础知识:
笔记目录总览
今天我们的学习目的是加深BX寄存器的了解以及学习一个新的重要指令:loop。今天的内容虽然非常简单,但是还是需要大家多多练习,增加熟练度。
一、 Bx寄存器与[偏移地址]
之前我们有讲过CPU寻址是通过段地址:偏移地址实现的,比如:
上图中2000:0010中的2000便是段地址,0010便是偏移地址,而再进一步,我们还可以通过DS段地址寄存器来替代段地址:
再之后,我们还是觉得这种方式不够灵活,于是我们又引入了[偏移地址]的方式来表示物理地址,当我们使用这种方式的时候,CPU会默认的取DS寄存器的值作为段地址:
但是随着我们学习的深入,我们发现单纯的通过[偏移地址]的形式已经无法满足我们的需求了,这种方式无法动态的去将一个变化的值赋与指令中,为了解决这个问题,我们需要引入一个新的寄存器来保存偏移地址的数据。之前我们有提到过四个通用寄存器:AX,BX,CX,DX。当时我们只是简单的提到过它们是四个通用数据寄存器,并没有详细的去讲他们的具体作用,今天我们就要用到它们中的BX寄存器。
让我们来整理下已经明确过用途的寄存器
1. 用来记录指令执行信息的
cs: 指令段地址寄存器
ip: 指令偏移地址寄存器
2. 用来记录栈信息的
ss: 栈段地址信息寄存器
sp:栈偏移地址寄存器
3. 用来存放数据信息的
ds: 数据段地址寄存器
今天要讲的BX寄存器 又叫做基地址寄存器(Base Register),通常被我们用来存放需要用到的存储器地址。它的用法很简单,只需要通过[BX]的格式即可表示取bx寄存器中数据的值作为偏移地址:
这里为基础不好的同学解释下上面都做了什么哈
1. 修改ds和bx寄存器的值
2. 修改ds:bx寄存器对应的存储空间的值
3. 将[bx]地址的值赋予ax寄存器,观察效果
4. 将bx寄存器的值赋予ax寄存器,观察效果
这里我们可以很明显的观察到,[bx]的表达形式与[ADRESS]相似,都是获取ds寄存器作为段地址,自身作为偏移地址的方式来表达物理地址。
二、 loop指令与jmp指令
1. jmp指令
loop指令是我们今天要新学的汇编指令,在学习前我们先整理下我们已经讲过的指令有哪些:
1. mov 赋值指令
2. add 加法运算
3. sup 减法运算
4. push 入栈
5. pop 出栈
再讲loop指令之前,我们需要先学习一下另一个汇编指令:jmp 。jmp指令是汇编语言中的无条件跳转命令,可以跳转到内存中任何程序段,偏移地址可在指令中给出,也可以在寄存器中给出,或者再存储器中给出。为了方便大家理解,我们还是提出一个小题目来讲解。
还记得我们只前举例说的求2的三次幂吗?让我们再来实现一次
只不过这次我们需要用编写源程序的形式来实现。
我们先来声明一个数据段:
codesg segment
...
codesg ends
然后接下来我们来实现下数据段的逻辑
codesg segment
mov ax,2H
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
codesg ends
然后让我们声明下数据段并设置终止点
assume cs:codesg
codesg segment
mov ax,2H
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end
我们来运行下:
注意点
- 我创建的源文件名称为codea,因此我在编译,连接阶段输入的指令为 masm codea 及 link codea ,如果不了解连接编译概念的请查看上一篇笔记:
[从零学习汇编语言] - 源程序与EXE文件
- debug追踪一直到mov ax,4c00H 指令就为止了,
mov ax,4c00H
int 21H
这两个指令为返回指令,暂时不讲,了解即可。
结果
通过三次加法模拟乘法运算,我们已经可以成功的求解出2的三次方的值,但是此时我们也发现这种实现方式存在一些弊端,如果我们想要求解2的10次方,岂不是要讲上述代码赋值10次?同理如果是100次幂,1000次幂呢?那我们该如何优化呢?这里就要用到刚刚要讲的jmp指令了。
使用前我们先介绍下jmp指令支持的格式:
1. jmp 段地址
2. jmp 寄存器
3. jmp 段地址:偏移地址
4. jmp 标号
让我们来改造下上面的程序:
assume cs:codesg
codesg segment
mov ax,2H
multi: add ax,ax
jmp multi ; 这里multi为上面设置的标号
mov ax,4c00H
int 21H
codesg ends
end
试一下运行结果:
这里可以观察到,我们已经可以没有次数限制的进入mov ax,ax代码段,无论我们想要获取2的多少次幂都可以了。但是这里还有一个问题,由于jmp指令是一个无条件跳转指令,在我们这个代码段中,它只会无限制的重复上述指令段,无法退出,那么我们有没有什么办法进行优化呢?
2. loop指令
loop指令是一种有条件的跳转指令,它需要在跳转前判断CX寄存器中的值,如果CX不为零则跳转至标号继续执行指令,为0则继续向下执行指令。
当cpu执行到loop指令的时候会进行两步操作:
1. cx =cx-1
2. 判断cx是否为零
loop指令支持的格式很简单
loop 标号 即可
而我们可以通过loop判断cx值的特性来实现有限制的跳转循环,接下来让我们改造下代码:
assume cs:codesg
codesg segment
mov ax,2H
mov cx,3H
multi: add ax,ax
loop multi ; 这里multi为上面设置的标号
mov ax,4c00H
int 21H
codesg ends
end
观察下结果(这里省下编译连接的操作,小伙伴不要忘了):
注意
我们已经观察到LOOP指令已经可以按照我们赋予cx寄存器的值3作为判断跳出循环,但是有的小伙伴会对运算结果有一些疑问:
这里会有小伙伴疑惑2的三次幂不应该是16吗,为什么这里会显示10?还记得我们说过DosBox的数值显示默认是十六进制的吗?那么十六进制的16对应的表示方法就是10,不信的小伙伴可以用windows自带的程序员计算器验证:
三、 一些奇奇怪怪的注意点
1. 汇编源程序的数字问题
讲到这里我们还要讲一个规则:在汇编源程序中,数据不能以字母开头。
比如:
我们不能够这样赋值: mov ax,ffffh
而是需要
mov ax,0ffffh
这里的0是为了防止以字母开头,而h则是表示当前数字为十六进制数
2. Debug和Masm的区别
(1) mov ax,[0] 问题
还记得我们之前在Debug的使用中讲过mov ax,[0]中的[0] 可以表示偏移地址吗?但是在Masm中并不支持这种写法,它会将这个指令直接编译为mov ax,0 。这个时候就需要我们将偏移地址放入bx寄存器中,通过 [bx] 的形式表达。我们也可以参考《汇编指令第四版》中的方法:
3. 段前缀
我们之前说过可以通过mov ax,[bx] 的方式获取物理地址对应的存储单元的值,其中偏移地址由bx寄存器给出而段地址则默认为bs寄存器给出。当然我们也可以显式的更改段寄存器,比如mov ax,cs:[bx] 。折现出现在访问内存单元指令中,用于显式的指明内存单元的段地址,在汇编语言中被称为段前缀 。
四、课后作业
1. 题目
2. 答案
问题一
assume cs:codesg
codesg segment
mov ax,0000H
mov ds,ax
mov bx,200H
mov al,00H
mov cx,3FH
flagA: mov ds:[bx],ax
add ax,1H
add bx,1H
loop flagA
mov ax,4c00H
int 21H
codesg ends
end
问题二
assume cs:codesg
codesg segment
mov ax,20h
mov ds,ax
mov bx,00h
mov cx,3fh
flagA : mov ds:[bx],bx
add bx,1h
loop flagA
mov ax,4c00H
int 21H
codesg ends
end
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
屏幕前努力学习的你如果想要持续了解博主最新的学习笔记或收集到的资源,可以关注博主的个人公众号。这里有很多最新的技术领域PDF电子书及好用的软件分享
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。