汇编系列其实也在一直更新,只不过更新的频率会挺慢的由于白天一直忙于工作,空闲时间还要看书、学习各种技术栈,早上也要抽时间早期健身,晚上回家还要陪家人学习,时间安排的满满当当,所以我就慢慢写,各位读者也别太着急,我其实真想再分一个自己出来。 之前的文章中介绍过〔0〕表示的是内存单元,它一般存储在ds寄存器中,偏移地址为0。比如下面的指令movax,〔0〕 就是将一个内存单元的内容送入ax,这个内存单元的长度为2个字节,正好存放一个字型数据,偏移地址为0,段地址在ds中。这种寻址方式相当于是直接寻址。 比如下面代码moval,〔0〕 就是将一个内存单元的地址送入al中,这个内存单元的长度是1字节,存放字节型数据,偏移地址位0,段地址在ds中。 所以要描述一个完整的一个内存单元,应该需要两种信息:即内存单元的地址和内存单元的长度。 比如我们要读取一个10000H的数据,你可能会需要下面这段代码。movbx,10000Hmovds,bxmoval,〔0〕 上面这三条指令就把10000H读取到了al中。 但是表示内存地址的方式不只有直接指定其内存地址,还可以用一种间接寻址的方式,比如〔bx〕,它表示的是一种寄存器间接寻址,也是一种偏移地址,同样的,比如我们要读取一个10000H的数据,使用〔bx〕这种方式的代码如下(假设ds1000H)movbx,1movax,〔bx〕 这样计算机就会寻找段地址为1000H,偏移地址为0001H的数据放入到ax中。 它的中文解释就是把〔bx〕指向的地址中的内容,送入ax寄存器中。 比如下面这段代码movax,〔bx〕 它表示的就是将偏移地址为bx的数据,送入到ax中,送入的内存单元地址是2个字节,存放字型数据。 又比如下面这段代码moval,〔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〕的寻址方式,比如下面代码movax,〔bx〕 bx中存放的数据作为一个偏移地址,这里用EA表示(没有其他意思,只是单纯地表示偏移地址),段地址在ds中,用SA表示(同EA的解释),将SA:EA处的数据送入ax中,即(ax)((ds)16(bx))。 可以将内存单元送入寄存器中,也可以将寄存器的数据送入到内存单元中,如下代码所示mov〔bx〕,ax 就是将ax中的数据送入到SA:EA处,即((ds)16(bx))(ax)。 为了让大家加深对〔bx〕的认识,我们通过一些汇编指令来认识一下程序的执行过程,代码如下movax,2000Hmovds,axmovbx,1000Hmovax,〔bx〕incbxincbxmov〔bx〕,axincbxincbxmov〔bx〕,axincbxmov〔bx〕,alincbxmov〔bx〕,al 下面我们就按照每一行指令来分析一下 首先,movax,2000H就是将2000送入ax中,movds,ax就是将设置段地址为2000H,movbx,1000H就是将1000送入bx中,movax,〔bx〕就是将2000:1000处的地址送入到ax中(因为段基址为2000,偏移地址dx为1000),2000H:1000H处的指令是00be,所以ax00BEH,存储字型数据,示意图如下 incbx就是将寄存器bx的值加1,此处有两条inc指令,所以执行完成后bx1002H,此处段基址:偏移地址为2000H:1002H。 然后下面(第七行指令)mov〔bx〕,ax就是将ax中的数据送入到〔bx〕中,也就是1002H处,指令执行后,2000:1002单元的内容为BE,2000:1003单元的内容为00,存放字型数据,执行完成后的示意图如下 继续执行第8、9行的指令,incbx,执行完成后bx1004H,然后执行第10行指令mov〔bx〕,ax,指令执行前:ds2000H,bx1004H,mov〔bx〕,ax相当于是把ax中的数据送到2000:1004处,指令执行完成后,2000:1004的单元内容为BE,2000:1005的单元内容为00,如下示意图所示 接下来执行第11行指令,incbx,执行完成后bx1005H,mov〔bx〕,al是把al中的数据送入内存2000:1005处,指令执行完成后,2000:1005处的单元内容为BE,如下示意图所示 继续执行指令,第13、14行指令和11、12行指令一样,它的意思就是将bx的值加一之后,将al的值送入到指定地址处,执行完成后的ds2000H,bx1006H,所以2000:1006处的内容是BE(al存储的数据),示意图如下 想必大家跟完上面的流程后,应该对〔bx〕这个间接寻址方式有了比较深刻的认识。 下面想个问题,使用汇编编程计算22,并将结果存储在ax寄存器中。 这个思路还是比较简单的,直接将2放在ax寄存器中,然后执行ax的add操作就可以了,下面是汇编代码assumecs:codesgcodesgsegmentmovax,2addax,axmovax,4c00hint21hcodesgendsend 上面这段代码中的计算量还比较低,但是如果要让你计算222222222呢,你难道要写n个addax,ax吗?assumecs:codesgcodesgsegmentmovax,2addax,axaddax,axaddax,axaddax,axmovax,4c00hint21hcodesgendsend 这就很繁琐啊,所以不能这么玩,那该怎么搞呢?这里就需要一种能够循环之星addax,ax的指令了,这个指令就是Loop。Loop指令 Loop指令能够循环判断是否执行指定的指令,它的执行流程就相当于我们Java中的for循环。 我们先来使用Loop改写一下上面n个2相乘的代码,然后再讲解一下Loop的使用。assumecs:codesgcodesgsegmentmovax,2movcx,8s:addax,axloopsmovax,4c00hint21hcodesgendsend 可以看到,我们使用8个2相乘的代码被优化的这么简单,这就是loop指令的精髓所在。 其实关键代码就是三条指令,即movcx,8s:addax,axloops 翻译过来的意思就是将8放在cx中,然后给addax,ax处设置一个标号,然后执行s循环。 loop指令的格式是:loop标号,CPU执行loop指令的时候,要进行两步操作,第一步:(cx)(cx)1,第二步:判断cx的值,不为0则转至标号(上面代码是s)处继续执行指令,如果为0则向下执行(上面代码中乡下继续执行就是movax,4c00h)。上面代码中,我们把8送入了cx中,也就是说,cx中存储的就是执行次数。 下面我们详细介绍一下上面这段程序的执行过程,从中体会一下cx和loops是如何配合实现循环的。 (1)执行cx,8,设置cx8 (2)执行addax,ax(第1次) (3)执行loops将cx的值1,此时(cx)7,(cx)!0,所以转至s处 (4)执行addax,ax(第2次) (5)执行loops将cx的值1,此时(cx)6,(cx)!0,所以转至s处 (6)执行addax,ax(第3次) (7)执行loops将cx的值1,此时(cx)5,(cx)!0,所以转至s处 (8)执行addax,ax(第4次) (9)执行loops将cx的值1,此时(cx)4,(cx)!0,所以转至s处 (10)执行addax,ax(第5次) (11)执行loops将cx的值1,此时(cx)3,(cx)!0,所以转至s处 (12)执行addax,ax(第6次) (13)执行loops将cx的值1,此时(cx)2,(cx)!0,所以转至s处 (14)执行addax,ax(第7次) (15)执行loops将cx的值1,此时(cx)1,(cx)!0,所以转至s处 (16)执行addax,ax(第8次) (15)执行loops将cx的值1,此时(cx)0,(cx)0,所以转至s处 (16)执行movax,4c00h(循环结束) 从上面这个过程中,我们可以总结处用cx和loop指令相配合实现循环功能的3点注意事项:在cx中存放循环次数。loop指令中的标号所标识的地址要在前面要循环执行的程序段,要写在标号和loop指令的中间。 所以综上所述,使用Loop和cx相配合实现的循环功能的结构如下:movcx,循环次数s:循环执行的程序段loops 比如我们想用Loop循环计算出123456这个值,就可以使用这种方式assumecs:codesgcodesgsegmentmovax,0movcx,456s:addax,123loopsmovax,4c00hint21hcodesgendsend 如果文章对你有帮助,小伙伴们三连走起呀!