汇编学习教程:循环和CX寄存器

引言

在上面博文中,我们主要学习了BX寄存器配合DS寄存器完成内存访问,同时也探究了Masm编译器面对弱指定和强指定两种情况时所产生的不同编译结果。

我们提到:xx:[idata] 是强指定格式,这种可以用于四种段寄存器中的任何一个,例如 ds:[idata]、es:[idata]、cs:[idata]、ss:[idata]。

此外,[bx]也是一种强指定格式,因为按照已经设计好的规定,[bx] 的格式只能和数据段DS配合进行内存访问,所以Masm编译器也可以正确识别 [bx] 格式并准确编译。

现在我们可以稍微明白四个通用寄存器 AX、BX、CX、DX的存在都会有一定的作用。现在到这里,我们已经学习了其中两个:AX寄存器主要用于各种赋值、数据传递BX寄存器则是配合DS进行便捷可变寻址

那么接下来,我们将学习CX寄存器的作用,它又会给大家带来什么样的惊喜呢?

本篇博文的学习目标:

1、学习汇编中循环的实现

2、CX在循环中起到的作用

3、编码感受循环

目标已定,那么就让我们赶快开始本篇的学习吧!

循环

循环是当今各种开发语言中的重要部分之一。循环的出现是为了解放人们劳动力,彻底释放机器的天性,提高事情的执行效率。

一般来说,循环由两部分组成:1、循环体,2、循环次数。循环体中是具体做的事情,循环次数则是这件事情做几次。汇编语言当然也不例外,它也存在着循环结构。

Loop指令

loop,中文意思就是循环,Lopp指令的格式是:

loop 标号

这里的标号是我们在程序中某一处设置的标号

CPU在执行Lopp指令时,首先会先将CX寄存器中的值减一,然后判断CX的值是否为零,不为零的话则跳转到标号处进行执行,否则继续向下执行。即:

执行顺序:

1、CX = CX - 1

2、判断此时CX是否为 0 

3、如果CX不为 0,则转到标号处执行(jmp 标号)

4、如果CX为 0,则继续向下执行

为了增强理解,我们可以使用 do{...}while() 循环结构来进行阐述:

int cx;

....
do {
  // 执行循环逻辑
  ....
  cx = cx - 1;
} while(cx > 0)
....

通过do while循环结构进行表达,我们发现是不是更加容易理解Loop指令执行过程了呢!

CX寄存器

通过上面对Loop指令执行过程的阐述,我们会发现,CX寄存器的值会影响到Loop指令的执行结果。CX寄存器中的值就相当于循环的次数,当循环次数为0(也就是CX寄存器值为0),则本次循环结束。

那么现在我们可以说,在汇编开发中,通常情况下我们使用loop指令和CX寄存器相互配合,来实现某段程序的循环。loop和标号之间为循环体,CX寄存器中存放的是循环次数。

我们可以从另外一个方面来增强记忆,CX,开头的C就是Count,次数的意思,所以CX寄存器的功能就是和次数相关。

编码实现

现在我们编程实现计算2的2次方,也就是2*2。计算机是通过加法来实现乘法,通过不断的累加便可以实现乘法运算。2*2,也就是两个2相加,即:2+2。

我们新建一个.asm文件,在Notepad++中打开,编写代码如下:

 代码很简单,我们编译连接后,在Debug中运行一下:

 实现了2*2,那么现在我们来编写实现2的3次方,也就是2*2*2,N*2可以使用N+N来实现,所以这里我们需要连续相加两次。编码如下:

 代码同样简单,只是增加了一条 ADD而已。那么我们编译连接执行一下:

 OK,我们得到了正确答案8。那么如果此时我们要求计算2的12次方怎么办呢?

2的12次方,相加的话也就是说我们要在程序中连续写11条ADD指令才行。你可能会说,11条而已,也不难办啊,那要是计算2的100次方呢,你难道要写上99条ADD指令嘛?显然这不可以,累都把人累死了,所以我们要使用循环来做这种重复性操作。

那么,编码如下:

现在我们来分析这段代码:

首先 mov cx,11,也就是说将会循环11次

标号s,表示循环开始的地方

在标号 s 和 loop 之间,我们写上循环逻辑:add ax,ax

loop 指令中的标号,一定要是循环开始处的标号:s

下面我们编译连接,在Debug中加载s3.exe,首先我们先使用U命令查看下此时内存中的汇编指令:

 我们注重看画红线的部分,可以看到,标号s经过Masm编译器编译后,变成了:0006H,而 0006H正是循环开始的地方所在当前代码段中的偏移位置。这也就是说,程序中的标号实际上就是表示为一个偏移地址,该偏移地址为标号所在的那条汇编语句在代码段中的偏移地址。上述程序中,标号s,即表示 add ax,ax 这条汇编语句在代码段中的偏移:0006H。

下面我们开始使用T命令单步执行:

 开始进入循环,此时CX值为:000BH,也就是11。我们继续执行:

我们可以看到,执行 loop指令后,CX值减一变成:000AH,由于A不为0,所以程序跳转到循环开始处:add ax,ax 。我们再次执行一次循环:

 再次循环后,CX值再次减一变成:0009H,由于9不为0,所以程序再次跳到循环开始处:add ax,ax。下面我们将程序一直执行到CX为1:

我们可以看到,CX为1 再次执行循环后,CX减一变成0,则结束了循环。此时我们可到了2的12次方值:1000H。

小结

通过上面的代码编写和程序运行观察,我们可以总结出下面几条:

1、在CX寄存器中存放需要循环的次数。

2、loop指令中的标号,一定要标识在loop指令的前面。

3、需要循环执行的逻辑,一定要写在标号和loop指令之间

正确的循环编写结构如下图所示:

这里博主要强调的是,在写循环结构时请严格按照上图中的结构来做,一旦放错位置带来的可能是运行错误的后果。

Loop和[bx]结合应用

现在有这样一个问题:编程实现将 ffff:0~ffff:d 这段内存空间中的数据加在一起,结果存放在dx中。

那么首先我们要考虑是否会存在数据溢出问题。dx寄存器是16位,最大数值为 FFFFH,0~d 共有12个字节,单个字节最大为:FFH,所以12个字节加在一起最大值为:FFH*CH = 0BF4H,小于 FFFFH,所以不会发成溢出,可以放心大胆的使用dx寄存器存放。

这里博主要提示一下,我们后续在开发的过程中,遇到数据的逻辑运算,请一定要注意溢出问题,事先要先判断数据的最大值和最小值,然后才能确定最终使用哪种寄存器来存放结果。

接下来我们思考,可不可以直接将数据累加到dx寄存器中呢?答案是不能直接累加,因为字节是8位,dx寄存器是16位,所以没办法直接累加。

那可不可以将数据累加到dh寄存器中呢?dh寄存器可是8位寄存器呀!答案是也不能,因为dh寄存器能够存放的最大值为 FFH,累加12个字节数据到dh中会发生溢出。

那该如何正确的累加数据呢?

首先,我们确定需要使用16寄存器来进行累加才能避免数据溢出的问题;其次我们要保证是字节数据相加,字节数据是8位,可以使用一个16位寄存器中的低八位来存放某个字节单元数据。

经过上面分析,思路已经比较清晰了,那就是:

数据累加:我们使用AX寄存器和DX寄存器进行配合

内存字节数据访问:使用BX寄存器和DS段寄存器来配合

循环实现:使用CX寄存器和Loop指令进行配合。

1、首先,将AX寄存器、DX寄存器赋值为0

2、将字节数据放到AL寄存器中,AH寄存器赋值为0,这样就把字节数据变成了字数据

3、将AX寄存器和DX寄存器相加,值放到DX寄存器中

4、完成一次相加(循环)后,BX寄存器值加1,访问下一个字节单元数据。

 OK,我们已经明确了编程思路,下面就可以愉快的写代码了~

实现代码如下:

assume cs:code     ; 声明代码段

code segment       ; 代码段开始
	
    mov ax,0FFFFH   ; 注意,源程序中,无法识别纯字母,需要字母前加上数字0
	mov ds,ax      ; 设置数据段为 FFFH
	
	mov ax,0H      ; 初始化AX寄存器值为0
	mov dx,0H      ; 初始化DX寄存器值为0
	mov bx,0H      ; 初始化BX寄存器值为0,这一步是为了从偏移位置 0 开始寻址
	mov cx,12      ; 12个字节数据相加,初始值为0,相加12次
	
  s:mov al,ds:[bx] ; 循环开始,将字节数据放到AL寄存器中
    mov ah,0H      ; 设置AH寄存器为0,将八位数据变成了十六位数据
    add dx,ax      ; 将DX、AX相加,值放到DX中
	add bx,1       ; 将BX加1,访问接下来的字节数据
    loop s	       ; 判断循环是否完成
	
	mov ax,4c00H   ; 
	int 21H        ; 程序返回
code ends          ; 代码段结束
end                ; 源程序结束

博主这里每条汇编语句都增加了注释,详细描述了每句具体的作用和实现意义,相信小伙伴都能看的很明白。那么接下来,就让我们编译连接,在Debug中运行一下吧:

 接下来我们看一下,内存中的汇编语句:

 加载的程序代码没有错误,那么接下来我们看一下 FFFFH:0H~FFFFH:BH 这段空间的内存数据:

还记得么,这就是那个主板日期呀~

现在一切都没问题,那么我们开始执行,观察每次循环后,对应寄存器的内容变化:

我们先一路T,执行到开始循环处: 

 现在我们先确定此时各个寄存器的值:

AX = 0000H

BX = 0000H

CX = 000CH

DS = FFFFH

 接下来我们执行一次循环后,观察相关寄存器的值:

 此时寄存器值变化如下:

AX:0000H -> 00EAH       EAH 是 FFFFH:0H 处的字节数据

BX:0000H -> 0001H        BX加1,那么下次循环中将访问到 FFFFH:1H 处的字节数据

CX:000CH -> 000BH       执行Lopp指令,CX减1

DX:0000H -> 00EAH       加上了 FFFFH:0H 处的字节数据

 接下来,我们进行第二次循环:

 此时寄存器值变化如下:

AX:00EAH -> 00C0H       C0H 是 FFFFH:1H 处的字节数据

BX:0001H -> 0002H        BX加1,那么下次循环中将访问到 FFFFH:2H 处的字节数据

CX:000BH -> 000AH       执行Lopp指令,CX减1

DX:00EAH -> 01AAH       加上了 FFFFH:1H 处的字节数据

 从上面的寄存器动态追踪观察可以看到,我们正在逐渐地将 FFFFH:0H~FFFFH~BH 这段内存空间的字节数据相加在DX内。

那么我们将循环执行完毕,来看下整个结果是否达到了预期:

 现在已经是加完了0~B十二个字节的数据,我们可以看到此时BX累加到了CH,DX值为0405H,CX此时为1,当再次执行Loop后,CX为0,则会跳出循环。

为了检验DX值是否为正确结果,我们可以手动累加一下:

EA+C0+12+00+F0+30+31+2F+30+31+2F+39 = 405H

说明我们的逻辑正确,实现了12个字节数据的累加。

快速通过循环

你可能会说,要循环12次,我要一直按好多次T,手好累好麻烦呀!我就只是想单纯的看下执行之后的结果是否正确,那能不能快速通过循环呢,一步到循环后呢?

当然了,还记得P命令吗?

P命令在之前的讲解中,我们使用它用来跳过程序返回程序。当然,它也可以使用到循环中,P命令直接执行完当前的循环,来到循环结束后。

请注意,使用P命令,一定要在Loop语句前使用。如图所示:

 我们可以看到,使用P命令后,直接来到了程序结束:mov ax,4c00H,而且CX值变成了0,DX寄存器变成了累加后数值的405H。P命令实现了循环跳过。

汇编开发总结

本次loop和 [bx] 联合应用例子,可以说的是第一次有逻辑实现的编程示例。通过这个示例我们可以发现,相比高级语言的开发,汇编开发还是有着自己独特的一面。

首先,汇编语言中由于寄存器资源限制,所以在开发中往往一个寄存器要身兼数职,既要干这又要干那。比如,在示例中,AX寄存器首先给DS寄存器赋值,然后在循环中又要做累加。

其次,每一个寄存器在身兼数职的同时,还要肩负一些特殊的工作。比如,在示例中,BX寄存器要配合DS完成寻址,CX寄存器要配合Loop指令完成循环。

本篇结束语

在本篇博文中,我们重点学习了汇编中循环的实现,同时完成了循环和BX寻址的结合应用。

那么接下来,我们将学习到如何在汇编中实现“声明变量”,在源程序中DS段的编写实现。

感谢围观,转发分享请标明出处,谢谢!

  • 14
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值