4.2.2数据寻址

数据寻址

在这里插入图片描述

大家好,本节中,我们要学习是数据寻址,上一小节学习了什么是指令寻址,即我们如何确定cpu接下来需要执行的下一条指令存放在什么地址,这是指令寻址需要探讨的问题。

cpu要执行的下一条指令永远都是通过程序技术器pc的值来指明的,我们介绍了顺序寻址和跳跃寻址两种方式,那这小节当中我们要学习的数据寻址指的是我们在运行一条指令的时候,一条指令它的地址码所指明的真实地址到底是什么,要确定地址码的真实含义,就是所谓数据寻址要探讨的问题。有同学会有疑问,难道给的地址码还有是假的吗,其实并不是这样,是因为我们有多种数据寻址的方式。

一、寻址方式举例

起始地址为0的寻址

在这里插入图片描述

我们还是结合上一小节的例子来进行分析,我们之前说过,一条指令,逻辑上由操作码还有若干个地址码组成,比如说,上一小节,我们介绍过例子,当cpu执行到jump这条指令的时候,他会知道,接下来需要把pc程序计数器的值改为7,也就意味着接下来要执行的那条指令存放在主存地址为七的地方,那对于这个例子来说,jump这条指令,它的地址码所指向的就是真实的地址,那对于这个例子来说,大家会发现我们给的程序,这一段代码,它是从主存地址为零的单元开始往后存储的,刚好jump指令想要跳转到的那条指令,它的地址就是七地址,我们把程序技术器pc的值直接改为7,并不会导致程序的运行错误,但是我们知道,计算机的主存里边,同一时刻有可能存在很多很多个程序正在并发的运行,这也就意味着我们不保证当前我们要运行的程序一定可以从地址为零的地方开始存储。

基于程序起始地址的寻址

在这里插入图片描述

假设我们要运行的这段程序是从主存地址为100的地方开始存储的,那么,对于jump这条指令来说,他的地址码的含义是不是就会出现错误,如果我们依然是按照之前的那种解释方式来解读地址码的真实含义的话,那当cpu执行到这条指令的时候,是不是意味着说我们执行了103这条指令之后,接下来,需要跳转到地址为七的地方,去运行地址为7的地方所存储的指令,那显然是错误的,因为我们程序是从100往后存储的,地址为七的地方,所存储的指令应该是从属于其他程序的一条指令,如果我们按照之前那种方式来解读地址码的含义,就会出现错误。
在这里插入图片描述

对于这个例子来说,我们要如何解读地址码七的含义呢,可以这么看,我们的这段程序代码是从地址单元100的地方开始存储的,这是程序的起始存放地址,所以这个地方的七我们可以把它解读为基于起始地址往后的偏移量,也就是说这个地址码的含义应该是100再加上七即100往后偏移七,那如果用这样的方式来解读地址码,就可以得到我们期待的运行顺序,故对于一条指令所包含的地址码,我们不能简单粗暴的认为地址码指向的就是真实的地址,有的情况下,我们需要改变对地址码的解读方式。

基于PC偏移量的寻址

在这里插入图片描述

我们再看一个例子,现在我们把这条jump指令的地址码改为三,当cpu执行这条指令之后,我们依然是期待接下来执行的是107这条指令,那这儿的三我们应该怎么解读?

当前cpu正在执行的是103这条指令,那上一小节我们说过,cpu每取出一条指令之后,都会让pc的指自动加一,那也就意味着当cpu执行这条指令的时候,pc应该是指向104这个位置,所以这个地方我们写的地址码三的正确解读的方式应该是从pc所指向的地址,往后偏移三个单位,那么,104再加三是不是就到了107,在这个例子当中,我们对地址码的解读方式又发生了变化,上边的解读方式是基于程序的起始地址往后偏移多少个地址,而现在种解读方式又是基于pc程序计数器往后偏移多少个位置,那通过接下的学习,我们会知道计算机常用的数据选址方式会有哪些。

二、十种寻址方式

现在,新的问题出现了,我们之前给出的指令由操作码和地址码两个部分组成,但是刚才我们说过数据的寻址方式,也就是地址码的解释方式、解读方式有可能会有很多很多种,那么我们如何区分一条指令的地址码,应该用哪种方式来解读它呢?
在这里插入图片描述

那接下来我们会学习十种数据寻址方式,也就是这十种地址码的解释方式,那为了区分,我们应该用哪种方式来解释地址码的含义。

在这里插入图片描述

通常我们可以在地址码的前边加上几个比特位,用来标识我们的地址码应该采用什么样的寻址方式,那我们会学习十种选址方式,只需要用四个比特来标识就可以,因此,我们在原有的指令结构的基础上,再增加上几个比特位,用来表示数据寻址的特征
在这里插入图片描述

根据中间几个比特的数值,我们可以确定我们的形式地址(A)应该用怎样的方式来解读它,来得到最终的真实地址,那我们也会把真实地址称为有效地址(EA),那这是对于一地址指令。
在这里插入图片描述

如果说是多地址指定的话,由于其中会包含多个形式地址,那我们对各个形式地址的解读方式会不一样,因此,每形式地址的前面,我们都会给他配上寻址特征,那接下来的讲解中,我们是要认识十种,数据寻址的方式,也就是十种形式地址的解读方式,因此我们只需要基于一地址指令来讲解分析就可以,多地址指令是一样的原理。
在这里插入图片描述
为了讲解方便,在接下来的讲解中,我们默认在我们讲解的计算机系统当中,指令字长等于机器字长等于存储自长,并且假设我们最终想要找到的数据,想要找到的操作数是3,那接下我们依次探寻各种各样的数据寻址方式。

1. 直接寻址

在这里插入图片描述

第一种方式叫做直接寻址,这种寻址方式是最符合我们直觉的,就是一个一地址指令,前边有几位操作吗,中间有四个比特用来表示接下来的形式地址应该采用直接寻址的方式来解读它。

(1)过程

所谓直接寻址,我们给出的有效地址a就是最终我们要找的操作数3所存放的真实地址,不用绕弯子,直接去地址所指向的主存单元去找我们想要的操作数就可以。
在这里插入图片描述

假设此时我们运行的是一条取数指令,那取数指令要做的事情就是去我们指明的主存地址当中取出操作数,把它取到acc累加寄存器当中,那我们要找的操作数三刚好放在a直接指向的地址当中,这就是直接寻址,是最直观的一种数据寻址方式。
在这里插入图片描述

我们来分析一下这条指令执行的过程,总共访存了多少次:
(1)第一次仿存是在取出这条指令的时候,需要一次访存;
(2)在执行指令的过程当中,会直接根据a所指向的这个地址去读出相应的数据,这儿又需要一次仿存。
那数据读出之后是放到了寄存器当中,写入寄存器,不算访存,因此总体来看总共访存了两次。

(2)优点:

直接选址这种方式执行起来还是比较简单的,cpu只需要把指令里边给出的地址,直接送到MAR即存储器地址寄存器当中就可以,不需要做任何地址的转换,并且指令执行的阶段我们只访问了一次主存;

(3) 缺点:

我们给出的形式地址a,它的比特位位数肯定是有限的,比如说我们的这一条指令,它总共就只有32个比特,那么也许我们给形式地址a分配的比特位也许就只有16位,16位二进制就意味着我们所能表示的主存的地址范围只能是
0到2^16-1,也就是说,寻址范围也许不会特别大
另一方面,如果说我们的操作数,它的存储地址发生了改变,就意味着,我们这条指令给出的地址就会失效,会指向错误的地方,除非我们修改这条指令的地址,因此,采用这种方式,当操作数的地址发生改变的时候就不太容易修改灵活性比较差

2.基于主存的间接寻址

在这里插入图片描述

(1)过程

在这里插入图片描述
指令里边给出的形式地址a,指向了某主存单元,在主存单元当中,存储了我们最终要取得的操作数的真正的地址,因此,我们在执行这条指令的时候,就需要总共三次访存。
(1)第一次仿存是取出这条指令;
在这里插入图片描述

(2)第二次仿存是根据a所指向的地址去读取主存的这个单元(A这个地址)里面存储的数据,而这个单元里面的数据才是我们最终要找到的操作数的存储地址;
(3)因此,接下还需要根据这个地址再进行一次访存来读出最终的操作数。
整个过程总共进行了三次访存,并且最终要取得的操作数,它的真实地址,有效地址是等于a括号。a表示的是地址,a外面打括号表示的是,这个地址所指向的主存单元里边的数据,那我们把a所指向的主存单元里的数据取出来,才是最终的有效地址,即真实的地址。

那刚才我们中间只折腾了一次,这种选址方式叫做一次间接寻址,那我们还可以再给他折腾一轮,进行两次的间接寻址。
在这里插入图片描述

在这里插入图片描述

指令当中带的形式地址a指明了第一个地址信息所存放的主存单元,那如果这个主存单元里面,它保存的第1个比特位是一,就意味着我们还需要继续的往下寻址,以存储单元里边存储的数据作为地址,去查找下一个存储单元。
在这里插入图片描述

那接下来这个存储单元,它的刚开始一位是零 ,就意味着存储单元所保存的些地址,就是最终的操作数的有效地址,我们再根据地址信息找到最终的操作数就可以,中间折腾了两次,这是两次间接寻址。
在这里插入图片描述

(2)优点:

好,那折腾么多有什么意义呢?首先,之前我们说过,对于刚开始介绍的直接寻址来说,指令的寻址范围会受到有效地址位数的限制,就比如说我们之前说到有效地址只有16位的话,那么采用直接寻址的方式,我们的操作数只能存放在0-2^16 -1的区间内。

在这里插入图片描述

现在如果采用间接选址的话,那么我们根据a所指的单元,找到了某个地址EA,那我们可以在主存当中存任意长度的数据,比如说这个EA我们用32个bit来表示,那这就意味着我们最终想要找的操作数,可以存放的地址范围就变成0-2^32-1的地址区间内,采用间接选址的方式,我们扩大了寻址的范围,那再看这种多次间接寻址有什么作用,这种选址方式可以便于我们编程。

就比如说,我们有这样三段代码,这三段代码会相互调用,那第一段代码在执行到某一句某语句之后,它会调用第二段代码当中的某一个函数,对于第二段代码来说,它在执行的过程当中,它又会再次调用第三段代码当中的某一段函数,好,现在第三个函数如果执行结束之后,是不是意味着第二个函数,它应该继续往后执行,当第二个函数执行结束之后,又会返回到第一个函数,从刚才执行到的那句代码继续往后执行,这种函数多层调用的应用场景其实和我们间接寻址,多级间接寻址的这种寻址方式是可以对应上的,所以我们说多级间接寻址可以便于我们编制程序,当然了,这里做简要的了解即可,对这个问题有兴趣的同学可以自己去研究一下汇编语言相关的知识。
在这里插入图片描述

(3)缺点:

很明显,当我们使用间接寻址的时候,特别是多级的间接选址的时候,我们在执行一条指令的过程当中会进行多次访存,每进行一次访存,我们才能取得下一级的主存地址,一级级的往下找,所以采用间接寻址的方式会导致指令的执行效率变低。

3.寄存器寻址

(1)过程

再看寄存器寻址,采用寄存器寻址就意味着,我们这儿给出的地址码,并不是指向了某个主存单元,而是指向了某寄存器,像Ri这种就是寄存器的编号,cpu内部会有很多通用寄存器,每个寄存器会有自己的编号。
在这里插入图片描述
那这里我们给出的形式地址指向了某寄存器的编号,把它翻译一下应该是对应十进制的9,意味着我们想要的操作数,在编号为九的寄存器当中,直接去寄存器里面找数据就可以,因此对于指令来说,我们只需要在取指令的时候进行一次访存,在执行指令的时候,我们只需要访问寄存器而不需要访存,因此采用种寻址方式,有如下优缺点:

(2)优点:

在这里插入图片描述

我们找到最终想要的操作数的速度是非常快的,另外,由于cpu内部的寄存器数量不特别多,因为寄存器很贵,那寄存器的数量不多就意味着寄存器的编号数目也不会特别大,我们就可以使用很短的几个比特,就可以表示所有寄存器的编号,整个指令的字长就会变得比较短,另外寄存器寻址还可以支持向量和矩阵运算。

(3)缺点:

价格贵,因为贵,寄存器个数不特别多,因此采用种方式寻址能力也是比较有限的。

4.基于寄存器的间接寻址

(1)过程

在这里插入图片描述

接下来我们开始套娃,下面一种寻址方式叫寄存器间接寻址,就是说我们指令里面给出的地址,指明了某寄存器的编号,就像刚才那样,而某寄存器当中所存放的内容EA,才是我们最终要找的操作数的实际的主存地址。
在这里插入图片描述

所以这个地方我们写的是EA=(Ri),Ri指的是第i个寄存器,外面打了括号j就是说这个寄存器里边的内容,是我们最终的有效地址EA,显然采用这种方式,取指令需要进行一次仿存,在执行指令的时候也需要一次仿存,那总共需要进行两次仿存。
在这里插入图片描述

(2)特点

可以看到,采用寄存器间接选址,比起我们之前提到的基于主存的间接选址来说,我们这次间接找地址的过程是不需要访存的,因此这种间接选址的方式要比一般的间接选址速度要更快。

5.隐含寻址

在这里插入图片描述

接下来再看比较特殊的寻址方式,叫隐含寻址,我们的指令想要操作的操作数,它的地址不是明显的显式的给出,而是在指令当中隐含着地址
在这里插入图片描述

这点大家结合我们之前提到过很多次的,在第一章第四个视频当中介绍的例子,就很好理解。有的指令他显示的给出的地址,只是指明了其中一个操作数存放在什么位置,那另一个操作数会默认隐含在acc累加寄存器当中,但是这另一个操作数的地址并没有在指令当中显示的指出,这就是所谓的隐含寻址,那显然,我们在指令当中只需要指明其中一个操作数的存储位置,样就可以缩短我们指令的指令字长,但是我们又不得不增加专门的硬件用来存储另一个操作数,这是比较奇葩的隐含寻址。我们想要的操作数不会显示的在指令当中指出,而是会用某一种潜规则来规定它到底在什么地方。

6.立即寻址

在这里插入图片描述

接下来看小节的最后一种寻址方式叫立即寻址,这个名称很容易和我们刚开始提到的直接寻址相混淆,直接寻址是说,我们直接去找形式地址所指明的主存单元就可以,而立即寻止这种方式也比较奇葩,我们想要的操作数会直接的被显式的写在指令当中,上面这串二进制数,把它翻译成十进制就是3,就是说我们想要找的操作数,会直接被包含在指令当中。
在这里插入图片描述

所以立即寻址这种方式,它的形式地址A,这部分并不是操作数的地址,而是操作数的值本身,那操作数又可以称为立即数,通常采用补码的形式来表示。寻址特征这一部分我们写了井号,大家在指令当中看到井号的时候,就意味着地址后面跟的段形式地址并不是地址,而是立即数,在很多汇编语言里面,如果说一个指令后面跟了井号,后面又紧跟了一个数字,那这样一串指令就是说后面跟的这串数字是立即数而不是地址,那也正是因为汇编语言里面经常用这样的方式来表示一个立即数,所以我们在计算机组成原理门课里,也会用井号来特别的标识,这是立即寻址的方式。
在这里插入图片描述
在这里插入图片描述
显然采用这种寻址方式,那么我们只需要在取指令的时候进行一次仿存,那接下来我们所需要的操作数直接被包含在了指令当中,所以执行指令的过程当中不需要访存,整个过程只需要访存一次,所以这种指令执行速度最快,不需要访存,甚至不需要访问寄存器。
在这里插入图片描述

但是它的缺点也显而易见,形式地址A的位数会限制我们立即数所能表示的范围。比如说a只有n位,那么当立即数采用补码表示的时候,他所能表示的数据范围 如上图所示。
在这里插入图片描述

好的,那这小节当中,我们介绍了几种数据寻址的方式,我们总共有十种数据寻址方式,但是由于比较多,我们需要把它拆分成多个小节来进行讲解,大家需要重点关注的是**如何根据形式地址得到最终的有效地址 **不同的寻址方式对形式地址a的解读方式解读规则也不一样,另外,表的最后一列,我们也给出了每一种寻址方式在执行指令的期间所需要的访存次数,注意这里我们排除了取指令所需要的那一次访存操作,我们在这给出的是指令执行的期间所需要的访存有多少次。

7.偏移寻址

(1)介绍

上小节中我们介绍数据寻址的六种方式,那在这个小节当中,我们会介绍剩下的三种数据寻址的方式,这三种寻址方式都可以归为偏移寻址这样的一个大类。
在这里插入图片描述

在这里插入图片描述

在上个小节刚开始的时候,我们举过这样的三个例子,最左边这个例子,我们可以采用直接寻址的方式,也就直接去访问当前这条指令所指向的位置,用这样的方式可以得到我们期待的结果,而第二个例子当中,由于我们这段程序,它起始的存放地址是从100这个地址开始存放的,所以我们对于jump这条指令的解析,对于七这个地址码的解释方式,就应该把它理解为从起始地址开始往后偏移七个单位,而第三个例子,我们应该把理解为是从当前pc程序计数器所指向的地址往后偏移三个单位。

所以右边这两个例子都有一个统一的特征,就是我们会以某一个地址,某一个特定的地址作为起点,然后加上我们形式地址所表示的这个偏移量,用这样的方式得到最终的有效地址,那我们这个小节会学习三种寻址方式,相对寻址,基数寻址,还有变质寻址,这三种寻址方式都可以被归为偏移寻址的一个大类,因为它们的共同特点就是,以某一个某一个特定的地址作为起点,然后偏移a这么多的单位,而a就是我们指令当中给出的形式地址。
在这里插入图片描述

那刚才我们提到的两个例子其实就是基址寻址和相对寻址,所谓基址寻址,就是我们会以程序的起始存放位置作为起点,然后再加上a这个形式地址作为偏移量,而相对寻址就是会以程序计数器pc所指向的地址作为起点,然后再加上a所表示的这偏移量,那还有一种偏移寻纸,是变址寻址,会以变址寄存器IX,这个寄存器里存放的地址作为起点,偏移a这么做的偏移量,所以,这三种偏移寻址的区别就在于,他们选取的这个起点,偏移的起点会不一样,那由于偏移的起点不一样,因此这几种寻址方式在实际当中的应用面也会不太一样,那接下来我们从上至下的顺序来依次详细的探讨这些寻址方式。

(2)基址寻址

在这里插入图片描述

首先来看基址寻址,在有的cpu内部,可能会有一个专门的基址寄存器,英文所写叫BR,指的是base address register,很多时候都会用两个英文字母的缩写来表示,这是一个什么寄存器,那有的寄存器有特定的作用,对于这些寄存器的英文缩写,大家也应该去关注一下,它的英文全称是什么,这有助于大家记忆每个计算器它到底有什么作用。

在这里插入图片描述

那如果一条指令采用基址寻址的话,指令当中会包含一个形式地址A,那这个基址寄存器会指向当前这个程序起始的一个存放地址,那最终的有效地址只需要用基址寄存器里存放的地址再加上形式地址a,也就是加上这个偏移量,就可以得到最终的有效地址。这里我们画了一个ALU算数逻辑单元,也就说把BR和A这两个数据送给算数逻辑单元进行一个加法运算,就可以得到最终的有效地址EA。
在这里插入图片描述

那EA又指向了我们指令想访问的操作数到底存放在什么地方,注意这儿的BR是一个专门的基址寄存器,那学会操作系统的同学应该知道,我们在第三章的第一个小节当中讲过程序的装入方式,在那个小节当中,我们讲过一个叫做重定位计存器,那操作系统里面提到的重定位计存器其实就是基址计存器。只不过是中文的翻译不一样而已,所以这个内容大家也可以和操作系统第三章第一节,那个视频进行着对比的学习好。
在这里插入图片描述

那刚才我们说的这种情况是cpu里面会专门的设置一个基址寄存器,那在有的计算机内部也许不会专门的给你设置一个基址寄存器,而是使用某一个通用寄存器来代替基址寄存器的一个功能,比如说总共有n个通用寄存器,编号分别为0-n减1,那么,这条指令当中指明了我们的寻址特征,是要采用基址寻址,另外还需要花几个比特位来指明我们的基地址存放在哪一个寄存器当中。如图中指明R0.

像这个例子当中,我们假设基地址是存放在R0这个通用寄存器内部,所以接下来这些地址变换的过程就是,取出R0里边存放的这个地址作为基地址,
在这里插入图片描述
把它送到ALU加法器里边,然后再把形式地址A作为偏移量也送到ALU里边,然后ALU通过加法运算得到最终的操作数的存放地址,所以,有的计算机内部是不会专门的设置一个基址寄存器的,在这种情况下,我们需要在指令内部指明,用哪一个通用寄存器来代替基址寄存器。

现在思考这样一个问题,既然这个属性是要指明某一个通用寄存器的编号,那么这个属性应该占几个比特位呢,很简单吧,我们得根据通用寄存器的个数来确定,这儿需要几个比特位,假如通用寄存器有八个,那么这个地方我们指明通用寄存器的编号是不是只需要,三个比特,因为二的三次方等于八,只需要三个比特就可以指明0-7这几个数,这个地方大家需要注意,考题里边有可能会遇到。
在这里插入图片描述

目前为止我们已经大致了解了基址寻址的一个硬件实现原理,接下来我们再来探讨为什么基址地址有它存在的必要,还是用我们第一章的第四个视频所提到的这个例子来进行讲解,这例子是很重要的。

在这里插入图片描述

之前我们说过这样的一段简单的高级语言写的代码,经过编译,链接等等一系列骚操作之后,形成了与之对等的一系列机器指令,数据指令和数据被无差别的以同等的地位存到主存当中。
在这里插入图片描述

那这个程序的第一条指令被我们放到了主存地址为零的这个地方,从零开始存放,也就是处于整个主存的低地址部分,整个程序在主存当中的地址范围是0-8。
在这里插入图片描述

那现在我们知道刚开始应该是要执行第一条指令,也就是存放在零号地址的这条指令,这条指令要做的是一个取数的操作,想要取出内存地址为五的那个存储单元里的数据,也就是a这个数据,把a这个变量取到acc累加寄存器当中,这条指令的地址码,也就是形式地址,把它翻译为十进制,刚好就是五,而五刚好就是变量a的实际存放地址,所以这条指令的数据寻址方式,我们采用直接寻址,也就是直接去访问五号地址,用这种方式处理,是可以得到期待的结果的。

在这里插入图片描述

但是现在问题来了,假设我们的这段程序,它是从内存地址为100的这个地方开始存放的,也就是说这儿的第一条指令存放在100这个地址,第二条指令存放在101,第三条指,102,一直往下,然后a这个变量是存放在105这个物理地址里面,所以对于刚才我们提到的第一条指令,我们想要把a这个变量的数据取到acc累加寄存器当中,我们对地址码的解析就不能采用直接寻址的方式,而必须采用基址寻址的方式。
在这里插入图片描述
基址寄存器BR会指向这个程序在内存当中的起始存放地址,也就是100,把这BR这串二进制转换成十进制,刚好就是100,那么按照基址寻址的规则,这地方的形式地址五应该再加上基地址,也就是100,用这样的方式来得到最终要访问的有效地址EA,那算出来的结果是105,刚好就指向了a所存放的这个主存地址105,那这样的话就得到了我们期待的结果,所以,基址寻址的意义就在于可以便于程序的浮动,就是说,这个程序可以从内存当中任何一个地址作为起始地址开始往后存放,当这个程序在储存当中的位置发生改变之后,操作系统只需要修改BR,这个寄存器里的内容就可以永远让BR指向当前这些程序的起始地址,那这样的话,我们写的这段程序就不用更改,始终保持原来的地址码就可以。
在这里插入图片描述

所以有了基址寻址之后,便于程序“浮动”,方便实现多道程序并发运行。因为主存里边有可能会有多个程序的数据同时存在,每个程序的起始存放位置都是不一样的,那么,每个程序运行之前,cpu的基址寄存器的值都会修改为当前运行的这个程序的起始存放地址,而这个信息通常是存放在进程控制块PCB当中的,这就是基址寻址。
在这里插入图片描述

需要注意的是基址寄存器BR,这个寄存器里的内容是面向操作系统的,只可以由操作系统或者操作系统的某些管理程序来修改这个寄存器的内容,我们普通的程序员是不可以操作BR寄存器里面的值的,因为我们写的应用程序,到底被放到内存的什么位置,这一点应该是由操作系统来负责管理的,我们决定不了,而基址寄存器又是指向我们应用程序的一个起始存放地址,因此基址寄存器的内容显然是应该由操作系统来负责管理,基址寄存器的内容作为基地址,然后指令里面给出的形式地址作为偏移量,把他们一相加就可以得到最终的一个有效地址。

还需要补充一点:普通程序员可以用汇编语言来直接操纵某一个通用寄存器里的内容,读或者写都行,但是,如果某一个通用寄存器被我们指定作为基址寄存器来使用的话,那么接下来这个通用寄存器里边的值,我们也不可以随意的修改,其中的内容会由操作系统来负责管理,总之,一个程序在内存里的基地址,无论是保存在专用的寄存器里,还是保存在通用寄存器里,这个信息都应该是由操作系统来负责管理的,我们普通程序员不能修改,也就是说作为普通的程序员,并不需要考虑自己的程序被存放在主存里的哪一块区域内,不管存在哪,操作系统都会帮我们解决这个基地址的问题。
在这里插入图片描述
采用基址寻址还可以带来一个好处,就是可以扩大我们指令的寻址范围,原本我们这条指令的形式地址A可能比较短,它的寻址能力有限,但是如果再加上基地址,由于基地址的位数比较长(等于寄存器位数),因此最终这条指令可以访问的有效地址的一个地址空间范围就可以扩大。

(3)变址寻址

在这里插入图片描述

变址寻址和基址寻址几乎一模一样,除了这个寄存器的名称不一样之外,其他的看起来都一样,那实现变质寻址所引入的这个寄存器称为变址计算器,英文所写是IX,也就是 index register,我们的一条指令给出了形式地址,那形式地址再加上变址寄存器里存放的地址信息,就可以得到最终的有效地址EA,和上一种基址寻址方式类似,有的系统当中会专门的设置一个变址寄存器ix,用来实现变址寻址,也有的系统当中会使用某一个通用寄存器来作为变址寄存器,原理和基址寻址类似,这里不再赘述。
在这里插入图片描述

变质寻址和基址寻址既然这么类似,他们之间有什么区别呢?区别就在于变址寄存器ix里边的内容是面向用户的,也就是说,我们可以自己修改ix里边的数值,另外,在这种寻找方式当中,ix一般会作为偏移量(被我们视为偏移量),而形式地址A 会被我们视为基地址,这点和上一种寻址方式刚好相反。
在这里插入图片描述

还是通过一个例子来了解变址寻址的作用,假设现在我们要实现这样的一个功能,就是把数组a0-a9十个元素分别进行加和,存到一个变量sum里面,这是我们高级语言的视角。
在这里插入图片描述

那如果说这段代码要把它用机器语言,或者说会编语言来实现,那我们可以采用这样的方式来实现,第一条指令是取数指令,把数取到acc累加寄存器当中,那这地方我们的地址码前边打了一个#号,我们说过,如果地址码前面打一个井号,那么说明,当前这条指令采用的是立即寻址,这地方会直接给出我们要使用的立即数。
在这里插入图片描述

那么,执行这条指令的结果就是会把零这个数,把它放到acc累加寄存器当中,同时,在这个例子当中,我们给一条指令只划分了操作码和地址码这样的两个部分,但实际上,这个指令当中还应该包含几个比特的信息,用来指明这条指令的寻址方式,那这地方我们就在前边用文字的方式把它标注一下。
在这里插入图片描述

下一条指令,要执行的是一个加法操作,就是用acc里边存储的这个值加上a0这个数组元素,把相加的结果放回acc累加寄存器当中,而这地方可以看到,a0这个元素是存放在主存地址为12的地方,所以第二条指令的地址码,我们只需要让他指向12就可以,这里用了十斤制表示,方便大家理解。
在这里插入图片描述
好接下来再往后的一条指令,同样是acc加法,就是把acc的内容和a1这个数组元素进行相加,再存回acc当中,a1是存放在13这个地址,因此这个地方地址码应该是对应十进制的13,后面都类似,我们要实现这个循环里边的十次加法操作,可以用十条加法指令来实现,然后最后再把acc里边累加得到的结果放回sum这个变量里边,sum变量是存放在22这个地址,所以这地方我们能够感受到,对于这种循环类的操作,如果按照我们之前知道的这些土办法,那么每一次循环都需要对应一条指令,这就会导致我们编程很不方便、很不灵活,万一未来我们需要实现a0-a20,的一个相加,那是不是按照这种方法,我们还需要继续在这条指令的后面再增加一些加法指令,这就导致了编程极大的不方便,因此我们就要需要引入变址寻址,来实现我们的循环操作。
在这里插入图片描述

引入变址寻址之后,我们刚才这段程序可以把它改造成这样子,用六条指令就可以实现,一会我们再来依次解释这些指令分别会起到什么样的作用,我们先给每一条指令标明一下这条指令所对应的寻址方式。

在这里插入图片描述
首先,第一条指令是取数指令,把立即数零放到acc累加寄存器当中,第二条指令同样也是取数指令,只不过是把它取到变质寄存器ix当中,同样是把零放进去。
在这里插入图片描述
然后是加法指令,这条加法指令采用了变址寻址,那变址寻址就意味着有效地址应该是等于变址寄存器里的地址IX加上形式地址A,那这条指令的形式地址A(地址码)指向了7这个位置,也就是数组的第一个元素的存放地址,然后现在变址寄存器ix的值是零,因此现在我们执行这条加法指令,导致的结果就是acc里的内容0,加上7+IX=7,也就是7这个单元里边的数据,((7+IX)就是取括号里地址里面的内容),也就是加上了a0的值,然后把这相加的结果放回acc寄存器当中,如果对照高级代码来看的话,就是完成了第一轮的循环。
在这里插入图片描述

接下来执行后面这一句,这一句同样是要进行一个加法操作,只不过加法的对象是ix变址寄存器,要让变址寄存器的值加一,然后再把这个值放回变址寄存器。
在这里插入图片描述

有点类似于我们执行i++的操作,接下来,当i小于10的时候,我们还应该让这个循环继续。
在这里插入图片描述

那站在汇编语言的角度,我们可以使用一条比较指令来比较ix里边的值,和10相比谁更大,接下来根据这两个数的比较结果来确定我们需不需要进行跳转,当ix的值小于十的时候,也就是高级语言中i小于10的时候,我们需要让程序的执行流跳转回二这个位置,那二这个地址对应的就是acc的加法指令,也就是要进行第二轮的循环,第二次的加法,第二次执行这条加法指令,这条指令采用变址寻址的方式,那么就意味着我们的操作数应该是存放在7+上1,这个变址,也就是存放在八这个位置的数据,八这个位置刚好存放的是a1即第二个数组元素,所以,执行了这条加法指令之后,就会导致acc里的值变成了a0+a1的值,进行了第二轮的循环。
在这里插入图片描述

接下来是类似的(执行345这几条指令),我们会让ix变址寄存器的值加一,由一变为二,然后同样的,需要判断这个变址寄存器的值是否小于10,如果他小于10的话,那么同样的,我们还会再跳转回二这条指令,也就在是执行下一轮的循环。那后续都是类似的。
在这里插入图片描述

多轮循之后ix的值会逐渐的增加为九,那么刚好a9这个数据是存放在7+9=16这个地方,那完成了最后这个数字元素的加法之后,ix同样的会进行一次加法,变成10 ,接下来,ix和十这个立即数进行比较,但是此时,10-(ix)的值已经不大于零了,没有满足这一条件,因此,这个条件跳转就不会发生,就不再跳转回二这条指令,而是会让这个程序继续往后执行,那最后这一句是把acc里的数存放到地址为17,这个地方,这地方刚好是sum变量,所以这样就完成了多个数组元素相加,然后赋值给sum变量这样的一个操作。

那现在再回头来体会之前说过的那句话,在指令当中给出的形式地址A这个地址,我们应该把它视为一个基地址,一个起点,然后IX变址寄存器,我们应该把它视为偏移量,从基址往后偏移多少个单位,所以引入了变址寻址之后,我们在处理数组元素的时候,可以把A这个形式地址设置为数组的首地址,然后不断的改变变址寄存期ix的值,让他不断的往上加,这样的话就可以很容易的对数组的所有元素进行访问,因此,有了变址寻址之后,可以很方便的让我们实现循环程序。这就是变址寻址的作用。

变址寄存器里的值,是我们普通程序员可以采用指令的方式来进行修改的,这点和基址寄存器是不一样的,ix的值和立即数10如何进行比较?这是在硬件层面实现的一个逻辑,我们暂时还没有细讲。这个问题先留下,在这个小节的最后我们再来补充,这地方大家只需要知道,进行十和ix的比较,本质上就是硬件会进行10-ix的一个操作,然后,接下来,这个条件跳转指令会来判断刚才的这个计算结果是否大于零,如果大于零,满足这个条件,那么就会跳转回二这个地址,也就是把pc的值改为二,是这样的一个意思。
在这里插入图片描述

那我们再对变址寻址进行一个总结,通过刚才例子,应该能够体会什么叫面向用户,什么叫可以由用户改变,并且现在我们知道了为什么变址寄存器里边的那个值会被视为偏移量,而形式地址A会被视为基地址。

(4)基址&变址复合寻址

接下我们来看一种复合的寻址方式,就是基址寻址和变址寻址的一个结合。
在这里插入图片描述

在刚才这个例子当中,我们是默认了这个程序,它是从主存地址为零这个地方开始往后存放的,因此当ix等于二的时候,我们刚好可以访问到a2这个单元,因为基址7+变址2刚好等于9,可以访问到a2.
在这里插入图片描述

现在假设这段程序是存放在另一个地方,从内存地址为100的这地方开始存放,也就说第一条指令存放在100,第二条是101,102,那a二这个数组元素就应该是存放在109这个地址,那这个时候,我们执行这条加法指令,就不能只用变址寻址,而是需要结合基址寻址和变址寻址来得到最终的有效地址,那要采用基址巡视,就需要有基址寄存器BR,它指向了当前这些程序的起始地址100.
在这里插入图片描述

那之前我们已经知道了基址寻址和变址寻址的一个有效地址的计算方式,现在我们把这两种寻址方式结合。先进行基址寻址,那意味着我们需要先把当前这条指令当中给出的这个形式地址A,A=7,先把形式地址A加上基地址BR的内容,那br的值是100,所以先进行了基址寻址就应该是得到107,那可以看到,现在,107已经指向了a零这个元素,那接下来在基址的基础上再进行一次变址寻址,也就是再加上变址寄存器ix里面的内容,也就是加上2,那么就得到了最终的有效地,109,这样就可以符合我们的预期,因此在实际应用当中,往往是需要多种寻址方式复合着来使用的。大家可以把每一种寻址方式理解为是一种函数,因为每一种寻址方式都是要把形式地址A映射为一个有效的真实地址EA,只不过不同寻址方式它的映射规则不一样,那多种寻址方式的复合其实就相当于把不同的映射规则给结合起来,那这不就是数学里的复合函数吗。当然,这只是我自己的思维习惯,大家可以按照自己习惯的方式来理解,在课后习题里边,大家还会遇到各种各样的寻址方式的一个组合,具体的在通过课后习题来进行知识的补充。

9.相对寻址

那接下我们要认识这个小节的最后一种寻址方式,叫做相对寻址。
在这里插入图片描述

(1)过程

在相对寻址当中,我们会把pc程序计数器所指向的位置作为基地址,然后把形式地址A看作是一个偏移量,那这形式地址A可以是正的,也可以是负的,通常用补码的形式来表示。
在这里插入图片描述

来看一下这个图是什么意思,我们从主存地址为1000的地方,取出了这条指令,然后这条指令采用了相对寻指的方式,其中包含了一个形式地址A,之前说过,每当cpu取出一条指令之后,都会让pc的指加一,这儿的加一我们打了双引号。
在这里插入图片描述

意思是说,pc在取出一条指令之后,它应该指向下一条指令的存放地址,当前执行的指令存放在1000这个地址,那如果当前这条指令它的指令自长占两个字节,那我们就需要让pc的值加二,而如果当前这条指令指令字长是四个字节,那我们就需要让pc的值加四,所以pc在1000的基础上,有可能是加二有可能是加四,当然,还有可能是加其他的值,所以此时pc的值有可能是1002或者是1004,那我们为了讨论方便,我们就把它规定为1002,此时假设pc是指向了1002。

那现在cpu执行的这条指令,它采用相对寻址的方式,是要在当前pc的值的基础上偏移A这么多的数量,也就是1002的基础上再加上a的值,那a有可能是正式负,由于地址a是用补码表示,因此,当他为负的时候,是不是就相当于在pc的基础上减掉了某一个正数,也就说这次寻址可以从pc所指的这个地址往前偏移,或者往后偏移都是可以的,因为a可正可负,这地方想和大家重点强调的是,当我们取走一条指令之后,pc的值一定会自动的加一,会指向下一条指令。好,所以王道书上的那句描述是有点问题的。在这里插入图片描述
王道书上说,我们每条指令里给出的这个形式地址a,它是相对于当前指令地址的一个位移量,其实不是这样,应该是相对于下一条指令地址的位移量,因为cpu取出当前指令之后,一定会让pc的指自动的加“1”,自动的指向下一条指令,所以a应该是基于下一条指令的存放地址的一个偏移量,这点大家需要注意。

在这里插入图片描述

接下来我们还是通过一例子来理解相对寻址有什么作用,这是我们刚才给出的那一段指令,实现了a0-a9的一个加和,for循环的主体应该是对应这样的几条指令,2这句代码是实现了加法,然后后面是ix的加法,ix的比较,然后最后这条是一个条件跳转指令,当条件满足的时候,又会跳回二这条指令执行,也就是说我们黄色这一条,跳转指令采用的是直接寻址的方式,直接指明了我们想要跳转回的一个地址,现在问题来了,我们写代码的时候,有可能代码越写越多,然后你写的某一段代码是不是有可能会在你的程序里边调整它的位置?比如你的这段代码可能本来是放在程序的比较开头的这段位置,但我们写程序的时候,有的时候,写着写着你会想把它挪到下面的地方,这种需求在我们编程的时候是显而易见的。
在这里插入图片描述
所以接下我们要考虑的问题就是,如果你的代码现在越写越多,然后你想挪动这个for循环的一个位置,那站在汇编语言程序员的视角来看,就相当于要把这几句指令的位置给挪一下,以前是从二这个地址开始存放的,现在我们把它挪一下,假设我们把它挪到从m这个地址开始往后存放,然后,此时各条指令的这个操作码和地址码,我们都让它保持不变,那现在把注意力放到这条跳转指令.
在这里插入图片描述

当for循环的条件满足的时候,我们会跳转到二这个位置,这是我们之前按照直接寻址的方式来解析这个形式地址导致的一个结果,现在如果我们还按照直接寻址的方式来解析,是不是意味着,执行这句代码之后,pc会指向二这条指令,而这条指令并不属于我们for循环,因此,这样的执行流肯定会导致错误。
在这里插入图片描述
现在为了解决这问题,我们引入相对寻址,那相对寻址是在pc所指的地址的基础上进行一个偏移,目前cpu正在执行的是这个跳转指令,那么cpu取出这条指令之后,pc的值会自动的指向下一条指令,也就会指向m加四这个地址,现在为了让这个循环能够正常的工作,那我们所期待的结果是让pc的值从当前pc所指的这个地址M+4往回偏移四个地址,因为m加四再减四不就可以回到m吗。

在这里插入图片描述

所以我们把这条跳转指令的地址码改为负四,这地址码是用补码表示的,那现在这条指令我们让它采用相对寻址,因此,这条跳转指令真正要跳转到的地方、要跳转到的地址,就应该是pc加上A,也就是跳转到m,由于跳转指令本质上是在修改pc的值,所以就意味着会把我们经过相对寻址得到的这个有效地址这个值(M+4-4=M)把它赋给pc,让pc重新指回m这条语句,那这样就可以保证for循环的正常运行。
在这里插入图片描述

并且从今往后,无论这段for循环的这几句指令,我们把它放到程序的哪一段位置,只要我们采用了这种相对寻址的方式,那么这条跳转指令一定都可以让程序的执行流重新的跳转回加法这个地方,也就是说,从今往后,无论这一段汇编代码被我们放到了什么位置,我们都不需要再修改这个跳转指令的地址码。让他永远保持负四,并且采用相对寻址就可以得到正确的结果,因此我们说引入了相对寻址之后可以方便我们的某一段代码,在程序的内部浮动,把这段代码放到程序里的任何个位置都ok,我们都不需要再修改这个地址码。所以这是相对寻址的一个作用。

这地方细心的同学可能会发现我们的这段程序整体变长了之后,数组元素a零不再是存放在七那个地址,它有可能存放到后面的某一个位置,那这是不是意味着我们得修改上面这条加法指令的这些地址参数呢,显然,如果我们每一次挪动代码,都需要修改这个地址参数的话,那这对于程序员来说也是很麻烦的一件事,那现实当中是如何处理这个问题的呢,通常来说,我们可以进行分段,这地方又可以和大家以前学过的知识串联起来,我们可以把一个进程的数据,把它分为程序段和数据段,程序段就是只保存我们这些指令代码,数据段专门来存放我们的数据,比如说,这是一个独立的数据段,我们可以让数组a永远是从七这个地址开始存放的,那进行分段之后,我们想要访问的那些变量、数组元素,它在这个段里的相对位置就可以保持固定不变,也就是说我们之前写好的这条指令给出的形式地址,我们不需要再改变,让他继续保持为7就可以。所以通过这个例子,能够体会到我们把进程的数据进行分段的一个好处。

在这里插入图片描述

(2)优点

那我们再对相对寻址进行一个总结,采用相对寻址可以有这样的一些优点:
如果当前我们执行的这条指令,想要访问的操作数,或者接下来想要跳转的一个地址,它是随着pc的值变化而变化的,和pc所指的地址总是会相差一个固定值,在这种场景当中,就很适合用相对寻址来解决问题,可以方便我们进行程序的浮动,也就是一段代码在程序内部的一个浮动,就像刚才我们给的例子一样,相对寻址,经常会被用于转移类的指令。

再补充一点,之前我们说采用基址寻址也可以方便我们程序的浮动,那在基址寻址当中,我们所说的程序的浮动,指的是整段程序在内存里的浮动,而相对寻址这边,我们说的程序浮动,指的是一段代码在程序内部的浮动,他们是有区别的。
在这里插入图片描述

那这一小节当中,我们介绍了相对寻址,基址寻址和变址寻址,这三种寻址方式都可以归为偏移寻址这样的一个分类,原因就在于这三种寻址方式都是基于一个基地址加上一个偏移量来得到我们要访问的有效地址,那相对寻址通常会被用于转移类的指令,而基址寻址通常会用于多道程序并发运行,会由操作系统来管理基址寄存器里的值,而变址寻址可以方便我们实现循环程序或者对数组的访问,那这几种寻址方式在指令执行期间都只需要访存一次,那再强调对于相对寻址来说,我们每次取出一条指令之后,pc都会自动的指向下一条指令,所以相对寻指其实是相对于下一条指令的一个偏移,而不是相对于当前执行的这条指令的一个偏移,这点很容易出错,大家不要误解。这就是这小节的全部内容。

最后我们再来补充一个之前留下的小问题,就是硬件是如何对两个数进行比较的,比如说谁更大,谁更小,或者是否相等,首先,我们平时最熟悉的是高级语言,高级语言里面,我们经常会写什么,if a大于b,else 啥啥啥,那站在硬件的角度,这段高级语言是这么被处理的:

在这里插入图片描述

首先,cpu会执行一条compare指令,compare就是比较,比较的对象是a和b这样的两个数,实质上比较的过程就是用a减掉b,那a减b是不是有可能得到各种各样的值,有可能是零有可能是正数,有可能是负数,甚至有可能出现溢出等等各种各样的情况,那么这个减法操作的一个运算结果相应的信息,相关的信息,会被记录在程序状态字寄存器也就是psw这个寄存器当中,这个寄存器里面会有几个比特位用来记录上一次运算的结果,也就是a减b这次运算的结果,比如说这寄存器里的cf这个比特位是用来表示之前的这次运算当中有没有产生进位或者借位,如果说运算过程中最高位产生了进位或者借位,那么cf的值会等于一,否则等于0,另外还有一个比特位,叫做0标志位zf,如果说上一次运算的结果是零那么zf会等于1,否则zf等于0,还有一个符号标志sf,这个标志位表示的是上一次运算,如果结果为负,那么sf会等于1,否则为0,最后一个是溢出标志,如果运算结果有溢出,那么of会等于1,否则为0。

也就是说机器执行了compare这条指令之后,会把a减b的一个结果,是否有进位借位,相减之后是否等于零,相减的结果为正还是为负,是否发生了溢出,把这些信息都记录到psw这个寄存器里的某几个比特位当中,接下来,cpu就可以根据这几个标志位,这几个比特位进行条件判断,用来决定是否进行程序执行流的转移,那站在高级语言的视角就相当于a大于b,这个条件满足的时候,程序执行流会被转移到else所包含的这一系列的语句当中。

那站在硬件层面,我们执行这条if语句,相当于硬件帮我们做了一个a减b的一个一个操作,如果a大于b的话,那么这个减法操作是不是会导致0标志位zf应该是等于零,因为a大于b,那么他们相减的结果肯定是非零的,所以zf会等于零。另外符号位sf应该也是等于零,因为a大于b,所以他们相减肯定是一个正值。所以硬件电路会以这两个比特位作为输入信号,然后通过电路的处理,把pc程序计数器的值,把它指向else 所对应的第一条语句,那这样的话就完成了一个所谓的条件转移指令,那这再给大家补充一点,在汇编语言当中,条件跳转指令有可能会有很多种,比如说

在这里插入图片描述

je2这条指令表示的是,之前我们compel的这两个数,如果相等的话,那么接下来程序执流pc就让他跳转到二这个位置,j表示的是jump,然后e表示的是equal,当我们compel的这两个数一口相等的话,我们就跳转到二这个位置.

除了这个指令之外,还会有其他的一些条件转移指令,比如说,jg,第一个j同样是jump,然后第二个g表示的是greater,就是更大,当更大的时候,这样进行跳转,所以他会边语言就写成了jg,那这也就意味着,如果我们比较的这两个数,前一个数比后一个更大更greater的时候,就会jump跳转到二这个位置,也就让pc的值等于二是这样的一个意思。那除了这两种条件跳转指令之外还会有其他的很多很多种条件跳转指令,大家可以在参考王道书给的一个总结,好,那与条件跳转指令相对应的就是我们之前提到的这个无条件转移指令,或者叫无条件跳转指令,jump如果直接使用jump二,那么就意味着我们不会管任何条件,直接就让pc的直接等于二,是这样的意思。
在这里插入图片描述

好,那了解了这些知识之后,大家就能够知道为什么我们刚才这两条指令,会用这种方式来写第一条指令四这条指令是要和ix寄存器里的内容比较,用谁和他比较呢,用立即数十和他进行比较,那么比较的本质就是进行一个减法,减法的结果如何,会被存到psw当中,然后接下来这个条件跳转指令是来判断我们刚才的这个减法,结果是否大于零,如果大于零那么就让pc的值跳转到二这个位置,是这样的一个意思。

那现在大家应该就可以理解这两个指令是什么意思了,对了,再补充一点,有的机器当中也会把psw就是这个程序状态字寄存器称为标志寄存器,这术语在我们客户习题里面也会遇到好,那这就是用硬件实现数的比较的一个背后的原理,大家可以在琢磨消化一下好的,那这个视频的内容还是比较多的,并且还不是很好理解,,不知不觉就录了这么长的时间,希望同学们静下心来好好的吸收好的,那这真的就是这个小节全部的内容了。

10.堆栈寻址

(1)介绍

这个小节中,我们会学习最后一种的数据寻址方式——堆栈寻址。经过之前几个小节的学习,我们已经把前边的九种寻址方式,给搞定了。现在我们来看一下什么叫堆栈寻址。
在这里插入图片描述

在这里插入图片描述
如果一条指令采用的是堆栈寻址,那么就意味着,这条指令想要寻找的操作数会被存放在某一个堆栈当中,那什么是堆栈,我们在数据结构那门课当中已经学过:一种只可以从其中一端进行插入和删除的数据结构。cpu内部会有一个专门的堆栈指针sp,s是stack,p是pointer。
在这里插入图片描述

这个堆栈指针存在于一个专门的寄存器当中的,就是说,sp这个寄存器,它指向了栈顶元素。所以如果一条指令采用堆栈寻址,就意味着我们不需要显式的给出操作数的一个存放地址,他的存放地址其实是隐含的,隐含在sp这个寄存器当中。
在这里插入图片描述

系统的堆栈可以有两种实现方式,一种是可以设置一组专门的寄存器,每个寄存器存放一个堆栈元素,然后还有一种更经济的方式是采用主存,在主存中划出一片区域作为堆栈。

(2)用寄存器组(硬堆栈)实现

在这里插入图片描述

先来看用一组寄存器实现堆栈是如何操作的,假设我们用四个寄存器来实现堆栈,最上面,这个寄存器是栈顶,最下面这个是栈底,另外,系统当中会有一个专门的寄存器sp,这个寄存器指明了当前的栈顶元素所存放的位置,那假设此时整个堆栈已经存满了,栈顶元素就是r0所存放的这个元素,所以sp应该指向r0,由于只有四个寄存器,所以sp只需要用两个比特就可以表示所有寄存器的序号。
在这里插入图片描述
假设我们想要用堆栈里的两个栈顶元素来完成一次加法操作,我们会先把加数和被加数放到acc和x这两个寄存器当中,然后通过ALU的计算,把结果输出到另一个寄存器里面,来看一下怎么实现。
在这里插入图片描述
为了书写方便,我们记当前的栈顶单元为msp,就是sp所指向的这个存储的单元号,现在我们要把栈顶和次栈顶的两个数进行相加,那我们可以用一条汇编语言指令pop,弹出栈顶的元素,这个元素会被存放到acc寄存器当中,所以这地方也需要指明我们弹出的元素所存放的位置,把栈顶元素放到acc里边,那这就导致acc的值变成了0001,现在由于这个元素已经被弹出了,所以我们需要让sp的值加一,指向次栈顶的元素,这相当于我们在逻辑上把刚才这元素给删除了。
在这里插入图片描述

参与加法运算的第二个操作数就是1001,同样,我们用pop指令把栈顶元素弹出,放到x这个寄存器当中,这导致x的值为1001,同样的,弹出一个元素之后,我们需要让sp的值加一指向下一个元素,相当于我们在逻辑上删除了上边的两个元素,现在,加法运算需要的两个数已经准备好,接下来我们执行一条加法指令,加法的结果放到寄存器Y里边,那这样就会导致ALU开始工作,然后把相加的结果输出到Y这个寄存器当中,最后我们要把这次加法运算的结果压回栈顶。
在这里插入图片描述

压栈操作会使用到汇编语言的push指令,把Y寄存器里边的这个数据push压回栈顶,首先,这个操作需要我们把sp的值减一,让他指向一号寄存器。
在这里插入图片描述

然后再把Y的值放到当前sp所指向的这个寄存器当中,也就把这里面存储的数据由1001覆盖为1010,这样的话,我们就用原本这个栈里的两个栈顶元素相加,得到了一个结果,并且又重新压回了栈中。

我们执行的pop和push这两个指令,就是使用了堆栈寻址,这两个指令,要访问的操作数所存放的位置是被隐含在了sp当中,当我们要弹出一个栈顶元素的时候,就是要弹出此时sp所指向的元素,出栈之后,sp必须加一;而对于入栈来说,我们需要让sp的值减一,然后再把我们想要存的元素存到sp所指向的这个地址。这就是堆栈寻址。要操作的数的地址会隐含的用sp这些寄存器来指明。

刚才我们给的这个例子是规定,栈顶是处于地址更小的这个方向,那大家要能够写出出栈和入栈操作背后所需要进行的一些处理,特别是sp这个栈顶指针的一个变化。还有的题目有可能栈顶是在地址更大的方向,那这种情况下对sp的处理又和上面这种情况会有一些区别。

(3)用软堆栈实现

在这里插入图片描述

上边我们介绍的这个例子是专门的用了几个寄存器来实现堆栈,这种堆栈称为硬堆栈。还有一种堆栈的实现方式是软堆栈,就是会从主存当中划分出一片区域,用这片主存区作为堆栈,那刚才我们说到的pop还要push,就是弹出栈顶元素和压入栈顶元素,对于这两个操作来说,如果我们采用的是软堆栈,意味着弹出一个栈顶元素或者压入一个栈顶元素一定是需要进行一次访存的,而如果说采用的是硬堆栈,那由于我们这些栈的元素都是存放在寄存器当中,因此,无论对栈的压入还是弹出,都不需要进行访存。

(4)优缺点

显然采用寄存器实现的这种硬堆栈速度会更快,但是它的缺点呢就是成本会更高,而软堆栈速度会更慢,但是成本会更低。

在实际的系统当中,通常是采用软堆栈来实现的,我们程序运行的过程中和函数调用还有局部变量等,这些相关的信息都会被保存在这个程序所对应的软堆栈当中,所以,有了软堆栈,有了堆栈寻址,我们才可以实现函数的调用,至于函数调用的时候需要往堆栈里面压入或者弹出哪些信息,大家可以参考数据结构的算法空间复杂度那个小节的视频,当然在那里也只是做了比较简要的了解,但是也能帮助大家初步的了解堆栈是如何工作的,还有它在系统当中的一个重要性。

(5)回顾

在这里插入图片描述

那这就是最后一种数据寻址方式堆栈寻址,对于堆栈寻址来说,当我们要进行出栈操作的时候,当前sp所指向的地址,就是栈顶元素,也就是我们想要访问的那个操作数的实际有效地址,而如果我们要入栈的时候,得先把sp的值进行一个减一(或者加一)的操作,sp减一之后所指向的地址才是我们最终要写入的那个有效地址,所以对于堆栈寻址来说,入栈和出栈的时候,我们的有效地址的这个确定方式是不太一样的,当然了,sp的值加一还是减一这些事情是硬件帮我们自动完成的,所以我们也可以说,采用堆栈寻址的时候,有效地址就是sp所指向的地址。

那如果我们采用的是硬堆栈,那么在pop和push这两个指令执行的时候,就不需要访存,注意我们这里说的是指令执行期间,我们取指令是需要访存的,但指令执行期间不需要访存,因为硬堆栈是用寄存器实现的,而如果采用软堆栈实现,那么就需要进行一次放存,因为软堆栈是划出了某一块主存区域来作为这个堆栈空间,所以采用硬堆栈和软堆栈的访存次数会有些区别。

好的,那么通过最近三个小节的学习,我们学习了什么是隐含寻址,立即寻址,直接寻址,间接寻址,寄存器寻址,寄存器间接寻址还有相对寻址,基址寻址,变址寻址和堆栈寻址,唉,心好累,不过这些内容也正是我们第四章的一个最高频的考点,也经常在大题当中进行考察,所以这些内容大家还需要静下心来好好消化,另外也需要结合课后习题来熟悉这部分的内容的一个出题方式,以上就是关于数据寻址相关的所有的内容,大家也可以再结合这个笔记来分别回忆一下各种寻址方式,如果忘了,那么可以趁热打铁再回去复习一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值