汇编系列其实也在一直更新,只不过更新的频率会挺慢的。。。由于白天一直忙于工作,空闲时间还要看书、学习各种技术栈,早上也要抽时间早期健身,晚上回家还要陪家人 + 学习,时间安排的满满当当,所以我就慢慢写,各位读者也别太着急,我其实真想再分一个自己出来。
之前的文章中介绍过 [0] 表示的是内存单元,它一般存储在 ds 寄存器中,偏移地址为 0 。比如下面的指令
mov ax,[0]
就是将一个内存单元的内容送入 ax,这个内存单元的长度为 2 个字节,正好存放一个字型数据,偏移地址为 0 ,段地址在 ds 中。这种寻址方式相当于是直接寻址。
比如下面代码
mov al,[0]
就是将一个内存单元的地址送入 al 中,这个内存单元的长度是 1 字节,存放字节型数据,偏移地址位 0 ,段地址在 ds 中。
所以要描述一个完整的一个内存单元,应该需要两种信息:即内存单元的地址和内存单元的长度。
比如我们要读取一个 10000H 的数据,你可能会需要下面这段代码。
mov bx,10000H
mov ds,bx
mov al,[0]
上面这三条指令就把 10000H 读取到了 al 中。
但是表示内存地址的方式不只有直接指定其内存地址,还可以用一种间接寻址的方式,比如 [bx],它表示的是一种寄存器间接寻址,也是一种偏移地址,同样的,比如我们要读取一个 10000H 的数据,使用 [bx] 这种方式的代码如下(假设 ds = 1000H)
mov bx,1
mov ax,[bx]
这样计算机就会寻找段地址为 1000H,偏移地址为 0001H 的数据放入到 ax 中。
它的中文解释就是 把 [bx] 指向的地址中的内容,送入 ax 寄存器中。
比如下面这段代码
mov ax,[bx]
它表示的就是将偏移地址为 bx 的数据,送入到 ax 中,送入的内存单元地址是 2 个字节,存放字型数据。
又比如下面这段代码
mov al,[bx]
它表示的就是将偏移地址为 bx 的数据,送入到 al 中,送入的内存单元地址是 1 个字节,存放字节型数据。
[bx] 这种间接寻址的好处就是每次偏移地址不是固定的,这为我们接下来的循环指令奠定了基础。
为了更方便的描述后面,我们后面使用 ()
来表示一个寄存器或者内存单元中的内容。
这里需要注意一下,() 内的表示的元素一般有三种类型:
- 寄存器名,比如 (ax) 就表示 ax 中的内容,(al) 就表示 al 中的内容。
- 段寄存器名,比如 (ds) 就表示段寄存器 ds 中的内容。
- 内存单元的物理地址,比如 ((ds) * 16 + (bx)),一个 20 位的数据。
我们知道,寄存器存储的数据类型有两种,字型和字节型,字型数据一般用 ax 这类寄存器来存储,字节型数据一般用 ah 、al 这种寄存器来存储。
同样的,() 内的数据类型也有两种,字型和字节型。比如 (al)、(bl)、(cl) 这种表示的数据就是字节型,而 (ax)、(bx)、(cx) 表示的数据就是字型。
在了解完上述的这些知识点后,我们就可以来正式看一下 [bx] 了。
[BX]再来啰嗦一下 [bx] 的寻址方式,比如下面代码
mov ax,[bx]
bx 中存放的数据作为一个偏移地址,这里用 EA 表示(没有其他意思,只是单纯地表示偏移地址),段地址在 ds 中,用 SA 表示(同 EA 的解释),将 SA:EA 处的数据送入 ax 中,即 (ax) = ((ds) * 16 + (bx))。
可以将内存单元送入寄存器中,也可以将寄存器的数据送入到内存单元中,如下代码所示
mov [bx],ax
就是将 ax 中的数据送入到 SA:EA 处,即 ((ds) * 16 + (bx)) = (ax)。
为了让大家加深对 [bx] 的认识,我们通过一些汇编指令来认识一下程序的执行过程,代码如下
mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
inc bx
inc bx
mov [bx],ax
inc bx
inc bx
mov [bx],ax
inc bx
mov [bx],al
inc bx
mov [bx],al
下面我们就按照每一行指令来分析一下
首先,mov ax,2000H 就是将 2000 送入 ax 中,mov ds,ax 就是将设置段地址为 2000 H,mov bx,1000H 就是将 1000 送入 bx 中,mov ax,[bx] 就是将 2000:1000 处的地址送入到 ax 中(因为段基址为 2000,偏移地址 dx 为 1000),2000H:1000H 处的指令是 00be,所以 ax = 00BEH ,存储字型数据,示意图如下
inc bx 就是将寄存器 bx 的值加 1,此处有两条 inc 指令,所以执行完成后 bx = 1002H,此处段基址:偏移地址为 2000H:1002H。
然后下面 (第七行指令)mov [bx],ax 就是将 ax 中的数据送入到 [bx] 中,也就是 1002H 处,指令执行后,2000:1002 单元的内容为 BE,2000:1003 单元的内容为 00,存放字型数据,执行完成后的示意图如下
继续执行第 8、9 行的指令,inc bx ,执行完成后 bx = 1004H,然后执行第 10 行指令 mov [bx],ax ,指令执行前: ds = 2000H,bx = 1004H,mov [bx],ax 相当于是把 ax 中的数据送到 2000:1004 处,指令执行完成后,2000:1004 的单元内容为 BE,2000:1005 的单元内容为 00 ,如下示意图所示
接下来执行第 11 行指令,inc bx,执行完成后 bx = 1005H,mov [bx],al 是把 al 中的数据送入内存 2000:1005 处,指令执行完成后,2000:1005 处的单元内容为 BE,如下示意图所示
继续执行指令,第13、14 行指令和 11 、12 行指令一样,它的意思就是将 bx 的值加一之后,将 al 的值送入到指定地址处,执行完成后的 ds = 2000H,bx = 1006H,所以 2000:1006 处的内容是 BE(al 存储的数据),示意图如下
想必大家跟完上面的流程后,应该对 [bx] 这个间接寻址方式有了比较深刻的认识。
下面想个问题,使用汇编编程计算 2 * 2 ,并将结果存储在 ax 寄存器中。
这个思路还是比较简单的,直接将 2 放在 ax 寄存器中,然后执行 ax 的 add 操作就可以了,下面是汇编代码
assume cs:codesg
codesg segment
mov ax,2
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
上面这段代码中的计算量还比较低,但是如果要让你计算 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 呢,你难道要写 n 个 add ax,ax 吗?
assume cs:codesg
codesg segment
mov ax,2
add ax,ax
add ax,ax
add ax,ax
add ax,ax
。。。
mov ax,4c00h
int 21h
codesg ends
end
这就很繁琐啊,所以不能这么玩,那该怎么搞呢?这里就需要一种能够循环之星 add ax,ax 的指令了,这个指令就是 Loop
。
Loop 指令能够循环判断是否执行指定的指令,它的执行流程就相当于我们 Java 中的 for 循环。
我们先来使用 Loop 改写一下上面 n 个 2 相乘的代码,然后再讲解一下 Loop 的使用。
assume cs:codesg
codesg segment
mov ax,2
mov cx,8
s: add ax,ax
loop s
mov ax,4c00h
int 21h
codesg ends
end
可以看到,我们使用 8 个 2 相乘的代码被优化的这么简单,这就是 loop 指令的精髓所在。
其实关键代码就是三条指令,即
- mov cx,8
- s: add ax,ax
- loop s
翻译过来的意思就是将 8 放在 cx 中,然后给 add ax,ax 处设置一个标号,然后执行 s 循环。
loop 指令的格式是:loop 标号,CPU 执行 loop 指令的时候,要进行两步操作,第一步:(cx) = (cx) - 1,第二步:判断 cx 的值,不为 0 则转至标号(上面代码是 s)处继续执行指令,如果为 0 则向下执行(上面代码中乡下继续执行就是 mov ax,4c00h)。上面代码中,我们把 8 送入了 cx 中,也就是说,cx 中存储的就是执行次数。
下面我们详细介绍一下上面这段程序的执行过程,从中体会一下 cx 和 loop s 是如何配合实现循环的。
(1) 执行 cx,8 ,设置 cx = 8
(2) 执行 add ax,ax(第 1 次)
(3) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 7,(cx) != 0 ,所以转至 s 处
(4) 执行 add ax,ax(第 2 次)
(5) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 6,(cx) != 0 ,所以转至 s 处
(6) 执行 add ax,ax(第 3 次)
(7) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 5,(cx) != 0 ,所以转至 s 处
(8) 执行 add ax,ax(第 4 次)
(9) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 4,(cx) != 0 ,所以转至 s 处
(10) 执行 add ax,ax(第 5 次)
(11) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 3,(cx) != 0 ,所以转至 s 处
(12) 执行 add ax,ax(第 6 次)
(13) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 2,(cx) != 0 ,所以转至 s 处
(14) 执行 add ax,ax(第 7 次)
(15) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 1,(cx) != 0 ,所以转至 s 处
(16) 执行 add ax,ax(第 8 次)
(15) 执行 loop s 将 cx 的值 - 1,此时 (cx) = 0,(cx) == 0 ,所以转至 s 处
(16) 执行 mov ax,4c00h(循环结束)
从上面这个过程中,我们可以总结处用 cx 和 loop 指令相配合实现循环功能的 3 点注意事项:
- 在 cx 中存放循环次数。
- loop 指令中的标号所标识的地址要在前面
- 要循环执行的程序段,要写在标号和 loop 指令的中间。
所以综上所述,使用 Loop 和 cx 相配合实现的循环功能的结构如下:
mov cx,循环次数
s:
循环执行的程序段
loop s
比如我们想用 Loop 循环计算出 123 * 456 这个值,就可以使用这种方式
assume cs:codesg
codesg segment
mov ax,0
mov cx,456
s:add ax,123
loop s
mov ax,4c00h
int 21h
codesg ends
end
汇编更新了几篇文章了。
没错!cxuan 对汇编下手了
手把手教你汇编 Debug
手撕汇编。。。
如果文章对你有帮助,小伙伴们三连走起呀!
- 原来汇编中的循环是这么玩儿的
- 『现学现忘』Git基础 — 15、blob对象详解
- 新零售SaaS架构:组织管理的底层逻辑与架构设计
- 2022最新IntellJ IDEA的zheng开发部署文档
- 数据结构与算法知识点总结(5)查找树
- SpringBoot扩展点EnvironmentPostProcessor
- netty系列之:netty中的核心编码器bytes数组
- 我发现 Linux 文档写错了
- 【SpringBoot实战】实现WEB的常用功能
- day01-从一个基础的socket服务说起
- 【面试普通人VS高手系列】Spring Boot中自动装配机
- vue-mobile-template 前端开源框架
- java自带的四种线程池
- 一种高效的同态加密方案及其应用-解读
- SpringBoot 如何统一后端返回格式
- php获取地址中的省市区
- 08-11 作业 面对对象和自动加载 封装 继承 多态
- 获取 NodeJS 程序退出码
- php上传函数封装
- 补0818:数据库ddl,dml实操 及 新建用户表
- PHP文件上传函数封装
- 补0817:域名空间内类实现自动加载,use的作用
- 前端、后端、测试、研发经理必备技能-ApiPost接口
- react源码解读 getNextLanes
- [emerg] bind() to 0.0.0.0:XXXX failed (13: Permission denied)
- 判断是否为数组的 JavaScript 方法总结
- php 会话跟踪
- PHP命名空间
- php 重载与事件委托
- 文件上传的实例