一文秒懂汇编中的循环问题

汇编系列其实也在一直更新,只不过更新的频率会挺慢的。。。由于白天一直忙于工作,空闲时间还要看书、学习各种技术栈,早上也要抽时间早期健身,晚上回家还要陪家人 + 学习,时间安排的满满当当,所以我就慢慢写,各位读者也别太着急,我其实真想再分一个自己出来。

之前的文章中介绍过 [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 指令

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

汇编更新了几篇文章了。

汇编语言学习手把手的Debug教程

8086汇编开发环境搭建和Debug模式介绍(图文详解)

汇编语言Debug命令详解教程

到此这篇关于一文秒懂汇编中的循环问题的文章就介绍到这了,更多相关汇编循环内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解汇编语言RCL(带进位循环左移)和RCR(带进位循环右移)指令

    汇编语言是依赖于计算机的低级的程序设计语言. RCL(带进位循环左移)指令把每一位都向左移,进位标志位复制到 LSB,而 MSB 复制到进位标志位: 如果把进位标志位当作操作数最高位的附加位,那么 RCL 就成了循环左移操作.下面的例子中,CLC 指令清除进位标志位.第一条 RCL 指令将 BL 最高位移入进位标志位,其他位都向左移一位.第二条 RCL 指令将进位标志位移入最低位,其他位都向左移一位: clc                             ; CF = 0 mov bl

  • 汇编语言功能用循环累加实现乘法

    目录 问题1:编程计算2的2次方,结果存在ax中 分析:用2+2实现 问题2:编程实现2的12次方 分析:用loop实现 问题3:编程实现123*236,结果存在ax中 分析:用236相加123次的计算次数比较少,节约计算资源 问题4:计算ffff:0006单元中的数乘以3,结果存储在dx中 1.判断数据是否能够存储 2.判断数据相加是否能够位数相同 问题5:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中 1.运算的结果是否超出寄存器的范围 2.能否直接相加dx中的数据 问题6

  • 使用汇编语言实现if else 循环函数调用的具体方法

    需要使用汇编来演示如下代码 需要下载ollydbg汇编调试器 点击File-Open随意打开一个exe文件 我这里随便找到c:/windows/explorer.exe文件 这里EIP的值表示下一次运行需要执行的代码位置 双击 EIP红色地址 左边代码会自动跳转到对应的代码行 有了以下环节 接下来添加代码 如果替换的代码 占用的字节数 小于原始的代码数 会自动补充 nop空指令 一.实现 if else MOV EAX,1 表示将1立即数 设置给EAX寄存器 CMP EAX,1 比较EAX的值和

  • 从Go汇编角度解读for循环的问题

    Go常用的遍历方式有两种:for和for-range.实际上,for-range也只是for的语法糖,本文试图从汇编代码入手解释for循环是如何工作的. 问题 首先来看看几个令人迷惑的地方. 问题1:遍历过程中取值 func main() { arr := [5]int{1, 2, 3, 4, 5} for _, v := range arr { println(&v) } } 上面这段代码里,会打印出什么? 问题2:遍历过程中修改 arr := []int{1, 2, 3, 4, 5} for

  • Go 中的循环是如何转为汇编的(方法详解)

    本文基于 Go 1.13 版本 循环在编程中是一个重要的概念,且易于上手.但是,循环必须被翻译成计算机能理解的底层指令.它的编译方式也会在一定程度上影响到标准库中的其他组件.让我们开始分析循环吧. 循环的汇编代码 使用循坏迭代 array , slice , channel ,以下是一个使用循环对 slice 计算总和的例子. func main() { l := []int{9, 45, 23, 67, 78} t := 0 for _, v := range l { t += v } pri

  • 汇编分析 Golang 循环(推荐)

    女主宣言 今天小编为大家分享一篇关于Golang循环汇编分析的文章,文章中介绍了golang循环的汇编层面的处理,通过分析,我们可以更了解循环的实现.希望能对大家有所帮助. PS:丰富的一线技术.多元化的表现形式,尽在" 3 60云计算 ",点关注哦! 循环是编程中很强大的一个概念,而且非常容易处理. 但是,必须将其翻译成机器可理解的基本指令. 它的编译方式也可能影响标准库中的其他组件. 让我们开始分析一下范围循环 . 1循环汇编 范围循环可以迭代数组,切片或通道.下面函数展示了,对分

  • 一文秒懂汇编中的循环问题

    汇编系列其实也在一直更新,只不过更新的频率会挺慢的...由于白天一直忙于工作,空闲时间还要看书.学习各种技术栈,早上也要抽时间早期健身,晚上回家还要陪家人 + 学习,时间安排的满满当当,所以我就慢慢写,各位读者也别太着急,我其实真想再分一个自己出来. 之前的文章中介绍过 [0] 表示的是内存单元,它一般存储在 ds 寄存器中,偏移地址为 0 .比如下面的指令 mov ax,[0] 就是将一个内存单元的内容送入 ax,这个内存单元的长度为 2 个字节,正好存放一个字型数据,偏移地址为 0 ,段地址

  • 一文秒懂IDEA中每天都在用的Project Structure知识

    Idea这款开发工具的便利之一是很多配置项几乎可直接使用默认项.但针对不同的项目难免需要针对性的配置,本文带大家详细的梳理一遍Project Structure中各项功能,注意收藏,以备不时之需. 先说一下写本文的缘由,在项目中用Idea中打开一组SpringBoot项目,结果编译的结果和日志输出的地方与预期不一致,于是仔细研究了Project Structure的配置项,发现此处竟然有很多有用的功能,汇总分享给大家. Project Structure即"项目结构",它几乎涵盖了一个

  • 一文秒懂Python中的字符串

    摘要:本文将告诉您Python中的字符串是什么,并向您简要介绍有关该概念的所有知识. 因此,让我们开始吧. 什么是Python中的字符串? 我们许多熟悉C,C ++等编程语言的人都会得到诸如"字符串是字符的集合或字符数组"的答案. 在Python中也是如此,我们说的是String数据类型的相同定义.字符串是序列字符的数组,并写在单引号,双引号或三引号内.另外,Python没有字符数据类型,因此当我们编写" a"时,它将被视为长度为1的字符串. 继续本文,了解什么是P

  • 一文秒懂python中的 \r 与 end=‘’ 巧妙用法

    /r的用法与end=""用法 \r 表示将光标的位置回退到本行的开头位置 end="" 意思是末尾不换行 在python里面,print()函数默认换行,即默认参数end = '\n' for i in range(3): print("Hello World") 可以设置print()函数的参数end=''",从而实现不换行 for i in range(3): print("Hello World", end=&

  • 一文秒懂Java中的乐观锁 VS 悲观锁

    乐观锁 VS 悲观锁 悲观锁:总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁.写锁.行锁等),当其他线程想要访问数据时,都需要阻塞挂起. 乐观锁:总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改. 乐观锁在Java中通过使用无锁来实现,常用的是CAS,Java中原子类的递增就是通过CAS自旋实现. CAS CAS全称 Compare And Swap(比较与交换),是一种

  • 一文秒懂nodejs中的异步编程

    文章目录 简介同步异步和阻塞非阻塞javascript中的回调回调函数的错误处理回调地狱 ES6中的Promise什么是PromisePromise的特点Promise的优点Promise的缺点Promise的用法Promise的执行顺序 async和awaitasync的执行顺序async的特点 总结 简介 因为javascript默认情况下是单线程的,这意味着代码不能创建新的线程来并行执行.但是对于最开始在浏览器中运行的javascript来说,单线程的同步执行环境显然无法满足页面点击,鼠标

  • java中的Arrays这个工具类你真的会用吗(一文秒懂)

    Java源码系列三-工具类Arrays ​ 今天分享java的源码的第三弹,Arrays这个工具类的源码.因为近期在复习数据结构,了解到Arrays里面的排序算法和二分查找等的实现,收益匪浅,决定研读一下Arrays这个类的源码.不足之处,欢迎在评论区交流和指正. 1.认识Arrays这个类: ​ 首先它在java的utils包下,属于Java Collections Framework中的一员.它的初衷就是一个工具类,封装了操纵数组的各种方法,比如排序,二分查找,数组的拷贝等等.满足了我们日常

  • 一文秒懂C语言/C++内存管理(推荐)

    C 语言内存管理指对系统内存的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成毕竟麻烦的结果.本文将从系统内存的分配.创建出发,并且使用例子来举例说明内存管理不当会出现的情况及解决办法. 一.内存 在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序 A 并不能访问应用程序 B,当然一些特殊技巧可以访问,但此文并不详细进行说明.例如在计算机中,一个视频播放程序与一个浏览器程序,它们的内存并不能访问,每个程序所拥有的内存是分区进行管理的. 在计算机系统中

随机推荐