计算机组成原理第四章--指令系统第一部分:计算机的指令和寻址方式

第二章介绍了运算器的工作原理。第三章介绍了存储器的工作原理。现在我们来介绍一下控制器的工作原理。指令又称操作指令,是用来指示计算机完成某种操作的命令,是计算机执行的最小功能单位。一个计算机里面的所有指令就构成了计算机的指令系统。(这里要注意的是,不同的机器的指令系统是不一样的,所以程序打包出来也是不一样的,比如电脑是x86系统,而手机则是arm系统(苹果电脑现在也是arm系统))。

1.指令的基本格式

一条指令可以由两部分构成,分别是:
(1)操作码:指明要“干什么”。
(2)地址码:指明被操作的对象在哪里。

在这里插入图片描述
可以根据地址码的数目,操作码的长度,指令的长度和操作的类型对指令进行更加细致的分类。
在这里插入图片描述

1.1 根据地址码的数目分类

1.1. 1 零地址指令

顾名思义,这种指令没有地址码,只需要操作码即可。有两类:
(1)不需要操作数的,比如空操作,停机指令和关中断指令等。
(2)堆栈型的计算机,会把操作数放在栈顶和次栈顶,然后计算就通过后缀表达式来实现计算(这其实准确来说是编译原理这门课会细谈,没有学过的可以百度一下)。现在简单介绍一下后缀表达式:
用王道PPT的例子为例:
在这里插入图片描述
如图所示,我们把正常的写法: A + B − C ∗ D / E + F A+B-C*D/E+F A+BCD/E+F称为中缀表达式
下面的就是后缀表达式,计算机会依次读取这个后缀表达式里面的内容,先读A,再读B,然后先放在一个零时的堆栈缓存里面。等读到了一个操作数的时候,就会从前面的缓存里面拿出最前面的两个数字出来,执行操作(这里读到了+,于是执行A+B),然后把计算结果给放回到缓存中去(也就是放回栈顶)。它只需要2次访存(取,写)。

1.1. 2 一地址指令

一地址指令我们在前面就见过了,最开始的指令例子都是一地址指令,同样,它也分为两种操作:
(1)只需要单数操作:加1,减1,取反,求补等。这些运算其实是不需要额外的操作数的,你给它一个数字,跟计算机说要干嘛(甚至包括自加,平方等)它就可以完成(这其实是a=+a和a=a+a的区别)。
(2)需要两个操作数,但是其中的一个已经在某一个寄存器里面,比如上面例子里面的加减乘除操作。
附上王道的PPT截图:
在这里插入图片描述
需要注意的是,一地址指令会用新生成的数字去覆盖原本地址A1的数据

1.1.3 二地址指令

需要两个地址的操作通常有:逻辑运算和算术运算(这是没有寄存器,或者寄存器满了的情况)。
它需要四次访存,因为要比上面的一地址多读一次。
在这里插入图片描述
对于二地址指令而言,它会用新生成的数据去覆盖第一个地址A1的数据。
第一个地址也被称为目的操作数,第二个地址被称为源操作数。

1.1.4 三地址指令

如果不想原本数据本覆盖,那我们就必须使用三地址指令,它会将操作后的结果放到第三个位置去。
在这里插入图片描述

1.1.5 四地址指令

前面的几种指令只适合顺序执行的程序,但是如果要实现Switch语句,跳着执行程序,那就需要用四地址指令,即前三个指明了操作数和结果存放位,最后一个指明跳去哪里执行下一句。
在这里插入图片描述
其实三地址指令也可以跳转,只不过前半部分就成了二地址指令,二地址指令如果想形成跳转执行,就必须把前半部分变成一地址指令。

1.2 按照指令的长度分类

1.2.1 回顾一下有哪些“字长”

(1)机器字长:计算机运算单元,执行一次整数运算所能处理的最大位数。(同计算机是一样的)
(2)存储字长:一个存储单元能存多少位的二进制代码,通常看存储器的数据寄存器MDR的位数就行。(同一台计算机是一样的)
(3)指令字长:一个指令最大的长度。(其实不是固定的,不然也不会形成分类)

1.2.2 计算机的指令按照字长分类

(1)它是根据指令的字长是计算机字长的多少倍来区分的:半字长指令、单字长指令和双字长指令(指令越短,读取的速度就越快)。
(2)根据指令系统中所有的指令字长是否都相等,也可以把指令系统分为定长指令系统(长度一致)和可变长指令系统(长度不一)。定长的操作系统的译码电路较为简单,但是指令的灵活性不高,可变长指令系统的灵活性高,但是译码电路设计起来较为复杂。

1.3 按照操作类型分类

这里只是了解一下,后面会一一细讲。
(1)数据传送指令:分为LOAD(把存储器中的数据放入到寄存器中)和STORE(把寄存器中的数据放入到存储器中)两种。
(2)算术逻辑操作指令:算术包括–加减乘除,加一,减一,求补,浮点运算和十进制运算。
\quad\quad\quad\quad\quad\quad\quad\quad\quad 逻辑包括–与或非、异或、位求反,位清除等。
(3)移位操作指令:包括算术移位,逻辑移位和循环移位。
(4)转移操作指令:无条件转移指令,条件转移指令,返回指令,陷入指令等(后面会慢慢学,这里不急)。
(5)输入输出类指令。
在这里插入图片描述

1.4 扩展操作码指令格式

1.4.1 什么是扩展操作码指令格式

扩展操作码指令的意思是,指令的总长度不变,但是操作码和地址码的长度是可变的。 来举个例子理解一下:假设我们的指令字长是16位,一个地址的长度是4位,那么如果要这个指令是一个三地址指令,则最前面的四位是操作码,后面的12位都是地址。如果表示一个二地址指令,则 最前面的4位全为1(CPU会默认全1是无效),后面4位是操作码,最后8位是地址。同理,一地址指令,就是前8位都是1,零地址指令前12位都是1·。
在这里插入图片描述
这只是扩展操作码的一种设计方法,扩展操作码不管怎么设计,必须遵循以下的原则:
(1)不允许短码是长码的前缀:上面例子里面,三地址指令的操作码是0000-1110这就是短码,长码是比如二地址指令里面的1111 0000- 1111 1110。长码前缀就是1111,显然,计算机是认为1111无效的,所以短码里面出现了1111,计算机会认为这个码无效,从而把后面的地址码当作操作码。
(2)各个指令的操作码都应该是独一无二的。(其实是废话)
(3)最常用的操作可以把操作码设置短一点比如0001(这个不是硬性要求)。

1.4.2 第二种操作码的表现形式(常考考点,大题)

题目通常会和你说指令字长的定长是多少位,然后告诉你现在需要多少条三地址指令,多少二地址指令,多少一地址指令,多少零地址指令,让你分别写出它们的地址吗范围。在我们上面的例子里面,其实无论是哪种指令,个数都是 2 4 − 1 = 15 2^4-1=15 241=15种,但是实际上浪费了许多空间,现实中我们可以根据需要灵活变化。用王道PPT的例子举例:
在这里插入图片描述
在已知长度是16位的情况下,其实设计三地址指令是最简单的,因为需要15条,所以地址码必然是0000-1110,之后的二地址指令其实也没有难度,因为四地址指令有15条,那么按照前面的例子,我们只需要把11的二进制1011写出来即可(注意,地址都是从0开始,所以不是写12的二进制),那么就是1111 0000 -1111 1011。接下来一地址指令就很有难度了,因为是62条,所以我们写出61的二进制:1111 01,所以至少需要6位来表示操作,那么一共16位,除去4位的地址码,就剩下了12位的地址,除去6位,我们可以知道前6位全为1,后6位从0000 00 到 1111 01 (这里我们也可以验证,其实对于1011而言,大于它的都是11开头,所以不会和前面的重合)。最后的零地址指令也很简单了,需要5位操作码,那么前面11位全为1,后面5位用来表示操作码即可,范围就是00000-11111。

总结一下,不管让你求什么,都只需要三步:首先,跟据这是几地址指令以及操作码需要几位,确定无效的操作码位是哪几位全设为1;之后,根据指令个数确定操作码范围;最后用A表示地址码。

题目也可能会让你计算指令的地址是否能够满足,这时候就可以利用这里学的另一个规律计算:假指令的每一段长度均为n,上段如果留出了m种状态,那么下一段就有 m × 2 n m\times 2^n m×2n种状态。光这样看可能有点抽象,我们用上面的例子来说明:上面一个16位的指令被我们分成了4段,如果是二地址指令,则前面一段(一地址指令的时候),留下来的 m = 2 4 − 15 = 1 m=2^4 -15=1 m=2415=1,所以这一段有 1 × 2 4 = 16 1\times 2^4=16 1×24=16种变化,但是它只要求了12种,所以余下的m=4,那么一地址指令其实就有 4 × 2 4 = 64 4\times 2^4=64 4×24=64种,但是也只用了62种,所以就剩下2种, 2 × 2 4 = 32 2\times 2^4=32 2×24=32,正好是32种,所以最后可以全部用完。如果计算过程中发现不够,那么就可以判断这个指令系统无法实现。

2.指令寻址(大题选择题都必考的重难点,重要程度五颗星)

指令寻址就是计算机在执行完一条指令之后,控制器如何知道下一条指令在哪里,这个操作其实是由一个叫做**程序计数器(PC)**的硬件来实现的,无论指令是什么,PC里面始终保存着下一条指令存放的地址。

2.1 顺序寻址

2.1.1 顺序执行的定长指令

对于一个顺序执行的按字编址的定长一地址指令,计算机有一个专门的程序计数器PC,每执行一条指令,这个PC都会在原来的地址上加1,这样PC就始终保存着下一条指令的地址。但是这是指令按照字编址的情况,如果是按照字节编址,那么一地址指令每一次就需要加2。看到这句话,脑子转不过来也不怕,我们详细说明一下:
(1)按字编址:一个字是由多个字节组成的,比如一个16位的计算机,字长就是16位。那么如果是一地址指令,地址码就占一个字节,操作码占一个字节,PC每次加1,其实这个1代表的是一个字,因为地址都是16位16位的。对于这样的情况,我们也可以称存储字长为2,每一次加1其实加的是一个存储字长。

操作码地址码

汇编语言其实就是按照这个思想,不过是把操作码给抽象成了英文缩写,地址码变为十进制而已:
在这里插入图片描述

(2)按字节编址:用字节编址来表示一地址指令,那么一个指令其实需要占用两个地址,像这样:

操作码
地址码

所以PC每一次需要加1其实只加了一个字节,需要加2才能定位到下一条指令。这样的情况其实存储字长为1。

总结一下,无论指令有多长,如果是顺序存储,并且指令字长等于存储字长,并且地址的编码是根据指令字长的大小编的,那么PC就是加1。总之,指令字长是一个编码地址长度的多少倍就加几说实话,这里不管怎么说都挺抽象的,有种只能意会,不能言传的感觉,可以反复地多思考一下这里的内容。再举个例子吧,如果是一个32位的指令,按照字编址,那么如果是顺序存储,PC存下一个指令就应该是加1,如果是按字节编址,那么32除以8等于4,所以PC需要加4。(说实话,这里的部分题目考察的其实是你对于字和字节的认知,一个字节是8位,一个字可能是多个字节组成,所以题目会和你说按字节编址,且指令长是16位,PC每读一个字节进1,那么读完一条指令,PC加多少,肯定是加2呀)。

2.1.2 顺序执行的可变长指令

如图是一串可变长的指令集,相同的颜色代表它是同一个指令:
在这里插入图片描述
其实,对于CPU而言,是不知道当前的指令有多长的,但是它知道这个指令从哪里读起又从哪里结束。以上图为例,这是一个按字节编址的指令那么CPU每读一个字节(每取址一次),PC就会自动的加1,CPU读完,则CP也正好定位到下一条指令如果是按字编址,那么CPU每读一个字,CP就自动加1,也是读完一条指令能保证定位到下一条指令的位置。

2.2 跳跃寻址

跳跃指令通常在CPU读取到跳跃操作的时候发生,它会强制的把CP里面的值改成要跳跃的值。还是上面的例子:
在这里插入图片描述
这里前面可能是顺序执行的,到了3的时候,CP会自动加1变成4,但是CPU因为读取到了JMP指令,会强制地把CP里面的数字改成7,下一条指令就跳到了7。C++里面的continue,break和return其实都属于跳转指令(goto也是,但是C++里面不常用),它们会无视本来后面跟着要执行的代码,去执行新的代码。

2.3 偏移寻址(选择题考过)

除了上面说的两种寻址方式外,还存在一种转移寻址,转移寻址是上面两种方式的结合,它会在地址码这里给出要偏移的量(也就是要移动的的距离),同时PC正常的按照顺序寻址计数,当读取下一条指令前,用PC的值加上偏移量。比如 ,我们按字节寻址,一个指令是3个字节,那么执行这个转移指令前,PC的值是9,转移量是7,那么下一条指令的地址就应该是9+3+7=19。

3.数据寻址

数据寻址,故名思意就是计算机如何根据一条指令中的地址码,找到对应的数据内容。操作码是不需要寻址的,因为每一个操作码仅代表一种操作。这里还需要明确的一点就是,在一条指令中的地址码代表的是逻辑地址,很多时候不是真实地址,需要转换,比如下图:
在这里插入图片描述
如果这段程序是从100到108,那么执行跳跃指令就会跳出程序导致出错,因为这个7其实是一个相对位置,如果我们想要获得正确的位置,我们需要在起始位置的基础上给地址码加7,所以跳跃的真实地址是107(说来说去,其实这就是上面说的偏移寻址)。我们把JMP的值改成3,就可以用上面的偏移寻址进行寻址操作了:
在这里插入图片描述
这里按照上面说的,因为是按字寻址,PC每次加1,所以 103+1+3=107。这两种方式都可以归结于偏移寻址,只不过一个是基于程序的起始地址开始偏移,一个是基于程序的当前PC计数器的值偏移。

数据偏移方式可能比你想得要多,目前一共十种,现在我们来依次学习一下,我们假设指令按字寻址,同时当前操作数为3,指令字长等于机器字长等于存储字长

3.1 怎么区分不同的数据寻址方式

为了区分不同的数据寻址方式,我们在原本地址码的基础上进行扩展,把地址码拆分为寻址特征+形式地址的形式(以后A表示形式地址,寻址找到的真实地址用EA表示)。
在这里插入图片描述
寻址特征用4比特的寻址方式位表示(因为有10种寻址方式,所以至少需要 2 4 2^4 24才能装满)。
多地址指令也就变成这样:
在这里插入图片描述

3.2 直接寻址

直接寻址没有什么好说的,直接寻址就是形式地址等于有效地址,即A=EA。比如要取一个数字,地址码为3,就说明这个数字存在地址为3的地方。
直接寻址的优点是:简单快捷,指令执行时仅仅需要一次访存。缺点是地址能表示的范围有限,比如主存是32位的,但是指令的操作码占用了4位,那么地址码只能表示28位前的地址,28位后的地址就无法表示,这是不合理的,同时,如果操作数被修改了,那么整个指令都需要被修改。

3.3 间接寻址

间接寻址就是为了解决直接寻址的问题而诞生的,它用一整个字长来存储数据的地址,然后指令里面的地址指向这个地址。比如说,我们的指令里面的地址码仅16位,但是要读取的真实的数据的地址有22位,那么就先用一个地址小于16位的32位内存块去存这个22位的地址,指令里面存了这个内存块的地址,读取指令的时候,计算机先找到这个内存块,再根据内存块内的地址找到对应的数据。还需要注意的一点是,有时候找到的内存块也可能是指向再下一个地址的(因为位置不够用),形成多级间接寻址(寻几次是根据存储字的最高位决定的)。 它的优点是大大扩大了寻址的范围。其实高级程序语言里面,程序的返回,函数之间的跳转也是用间接寻址实现的。
它的缺点很明显,需要多次访存。

3.4 寄存器寻址

寄存器寻址的方法也无比简单,就是把数据存放在寄存器里,然后地址码存的是寄存器的编号。这里的寄存器其实就是CPU里面的通用寄存器,一个CPU可能有16到64个寄存器,每一个都有专门的编号。
在这里插入图片描述
寄存器寻址的优点就是指令的执行过程不需要访存,而且CPU访问寄存器的速度是极快的,所以这种执行的速度很快,并且寄存器的数目不多,所以其地址码也很短,同时还支持向量和矩阵运算(了解一些就行)。
缺点就是寄存器高昂的价格导致能存放的东西不多,所以寻址能力受限。

3.5 寄存器间接寻址

寄存器间接寻址就是把要寻找的真实地址保存在一个寄存器中,这样,比直接间接寻址就要快很多。流程就是CPU取到指令之后,会根据指令中的寄存器编号去到寄存器里面获取真实的数据地址,然后去主存中寻找。
在这里插入图片描述
它的优点就是比间接寻址要快,因为要少一次访存,缺点还是寄存器贵,不便宜,不能部署太多。

3.6 隐含寻址

隐含寻址其实就是一开始举例的例子里面,比如加法运算,在指令中只给了一个操作数,但是其实还有一个操作数被隐藏在计算过程中。比如:
在这里插入图片描述
优点是指令的长度可以变短,缺点就是需要额外的寄存器去保存这个隐含的操作数。

3.7 立即寻址

立即寻址可以用于操作数不是很长的情况:没有地址码,操作数直接就写在指令之中,根本不需要寻址。 那么这个写在原本地址码地方的操作数我们就可以称之为立即数。在汇编语言中,LOAD #123,如果数字前面有#,那就说明它是一个立即数。
在这里插入图片描述
优点是比前面的寻址方法都快,缺点就是立即数不能太长,否则指令装不下。

3.8 偏移寻址(非常重点)

一共有十种常用的寻址方式,前面介绍了6种,还剩下4种,这4种中的3种可以统一概况为偏移寻址, 偏移寻址已经在上面介绍过,因为很重要,这里再细谈一下。它们有共同的点是:均以某一个特定的位置作为起点进行偏移。他们的不同之处就是偏移的起点不一样:基址寻址以程序存放的起点为起点(前面的例子),相对寻址是以PC计数器所指向的位置作为起点(指令那里的例子),编址寻址的起点由程序员指定。

3.8.1 基址寻址

在CPU中有一个专门的基址寄存器(BR),基址寄存器负责存储程序的起始地址。在计算机寻址的时候,只需要用形式地址A的值加上基址寄存器BR的值,就可以计算出有效地址EA的值。
在这里插入图片描述
基址寄存器其实就是操作系统里面的重定位寄存器。有的计算机是没有基址寄存器的,但是不管是哪个CPU都有很多的通用寄存器,所以可以使用通用寄存器来取代基址寄存器,这个时候的指令结构也需要进行相应的变化,即除了保存形式地址外,还需要保存一个通用寄存器编号,在指令中表明使用哪个通用寄存器作为基址寄存器。
在这里插入图片描述
它的优点是便于多道程序的运行,允许程序的浮动,程序员只需要指明需要跳转到第几行代码就行,不需要了解这几行代码的真实存放位置是哪里。多核CPU也有多个BR,所以是可以允许好几个程序并行执行的,如果是多个程序在一个核上并发,则会根据当前允许的程序动态调整BR的值,每个程序的起始地址都存放在进程控制块PCB中。
最后,基址寄存器BR是不受程序员控制的,只能由计算机的操作系统或者管理程序来控制(如果能修改BR,那么比如登陆一个系统,起始位置是第0行,本来是要密码正确才能跳转到第11行,登陆某个账号,密码错误会跳转到第9行显示密码错误,但是你可以直接修改BR的值,改成2,那么密码错误也可以登陆成功)。

3.8.3 变址寻址

变址寻址和基地址寻址的区别就在于它的起始位置存放在变址寄存器(IX)中,在操作时,有效地址的值就是变址寄存器里面的值加上形式地址的值。同样的,好一点的CPU是有专门的IX的,但是差一点的CPU没有,这时候就需要用通用寄存器来代替,那么指令里面就需要附带上通用寄存器编号。

变址寻址和基址寻址的最大区别在于:变址寻址是允许程序员修改IX中的值的,这个时候的形式地址A往往是不变的(可能是一个const值,程序员无法修改),作为基地址,IX作为偏移量;然而基址寻址则不允许程序员修改BR寄存器里面的值,但是允许程序员修改形式地址A,所以BR作为基地址而形式地址A作为偏移量(两者正好相反)。

现在举个小例子来说明变址寻址的作用:
比如现在有一段程序:
在这里插入图片描述
如果使用直接寻址和立即寻址的方法来实现这个程序:
在这里插入图片描述
我们可以看到这里的指令部分占用了11行,如果我们把循环的次数多加1,则会占用12行,那么多循环几次(比如一个人工智能算法循环300万次),主存就被编译出来的指令给占满了,那就直接不能存数据了(python里面使用递归超过200W次就会报错也是这个原理)。现在引入变址寻址:
在这里插入图片描述
我们首先来理清楚一下逻辑:首先是一样的,使用立即寻址把数据存放到ACC累加寄存器里面,之后还把IX的值设为0,这两步操作其实就是初始化ACC和IX这两个寄存器。之后,因为a[0]存放在7的位置,所以直接用ACC的值加上7+0这里的a[0]。之后就是不一样的地方了,它直接把寄存器的值往下加1,然后进行了比较操作(其实比较操作的硬件实现是比加法要困难的,但是这里加一点费用实现这个可以节省更多的不必要开销)。之后如果符合条件则跳转回2号指令,IX加1之后正好就是a[1]的位置,换言之,变址寻址可以让机器语言也实现循环的操作,大大节省了内存的空间(在保证内存可以存下全部的数据的情况下,就是循环1亿次也不会因为指令太多而内存溢出)。

这里进行一下如何通过硬件实现比较操作的扩展:
比如我们需要执行一个比较操作:a>b 在计算机里面,其实执行了一个cmp指令,写成汇编是 cmp a,b。它在硬件中其实是使用减法器来执行a-b的操作,并且把计算结果存储在程序状态字寄存器(PSW) 里面。
先简单介绍一下什么是程序状态字寄存器(也被称为标志寄存器):PSW里面有多个比特位来记录不同的信息(其实在第二章讲加法器的标志位的时候讲过,现在复习一下):
(1)CF:进位/借位标志,在最高位出现进位或者借位的时候为1;
(2)ZF:零标志,在计算结果为0时为1;
(3)SF:符号标志,正号为0,负号为1;
(4)OF:溢出标志,有溢出为1,没有溢出就为0。
那么做比较只需要看PSW里面的状态即可,实际上cmp指令总是和条件跳转指令jxxx一起使用的(现在了解一下就行,在本章的第二部分会细谈),综合下来,大小的判断方式为:
在这里插入图片描述
这个图在学完第二部分以后再来看。

3.8.4 基址变址复合寻址

需要注意的是,不管是哪种寻址方式,都不会和其他的寻址方式形成对立的关系,而是要根据相应的情况来复合使用的。这里使用基址和变址的复合方式,可以更好的实现上面的程序,用王道的例子来看:
在这里插入图片描述
如果程序在主存中的位置不是从0开始的,那么我们就需要先用基址寄存器去先寻找它的真实地址,在真实地址的基础上再使用变址寻址的方法去实现循环,其实这个过程也不复杂,只需要再IX里面额外加上BR的值即可实现。

3.8.5 相对寻址

相对寻址的程序起始位置在PC程序计数器里,形式地址则作为一个偏移量。 学到这里,如果忘记了什么是PC程序计数器,就看看上面的内容,这里简单回顾一下,如果是按字寻址,那么PC每次前进一个字,按照字节寻址,则PC每读一个字的指令就加1,可以方便程序顺序执行,也就是取出一条指令之后,PC总是指向下一条指令的执行位置,但是在相对寻址里面,下一条指令的执行位置并不是PC的值,而是PC+A的值
现在举个例子来体现一下相对寻址的作用,要改写成汇编的代码如下:
在这里插入图片描述
那么使用变址寻址的方法来实现的时候,它是这样的(假设程序的起始地址是0):
在这里插入图片描述
现在如果程序员在for循环上面加了几行代码,则for循环就需要同时在汇编语言里面挪动:
在这里插入图片描述
可以从图中看出,M+3位置的跳转指令是讯息进行同步的修改的,应该把2改成M(我们假设从7开始存的还是数据,因为主存里面其实是把主存给分好段的,分为了程序的和数据段,数据段永远只存数据,而且位置不变,这样无论你怎么修改程序,只要不修改数据,数据就不会有改动)。但是这样每一次都需要手动去修改,就十分麻烦,所以就需要相对寻址来实现动态更改: 现在的PC指向的是M+4,那么就用PC-4就可以获得对应的M。就像下面这样:
在这里插入图片描述
这里还需要强调的一点是,PC计数器的值会因为相对寻址而改变,这样就可以保证PC永远指向的是下一条需要执行的指令的真实地址
所以相对寻址很适合用于处理转移指令基址寻址适合多道程序的并发执行。 变址寻址很适合循环结构的实现。相对寻址有利于代码在程序内的浮动,基址寻址适合的是程序在内存里面的浮动,变址寻址可以防止程序在内存里面因循环而变长。这就是他们三个的优缺点。

3.9 堆栈寻址

堆栈寻址的形式地址并不指向一个数据的地址,而是指向了一个栈顶指针(SP),在程序执行的时候,CPU会根据这个栈顶指针找到栈顶元素,并且把它出栈。
现在来详细的讲解一下和堆栈寻址相关的栈方面的内容。
首先,栈顶指针SP是存放在一个十分特殊的寄存器里面的,所以CPU读取它的过程是很快的。然后用来存储操作数的堆栈其实还分为两种,一种是硬堆栈,一种是软堆栈。
(1)硬堆栈:

硬堆栈就是用一堆寄存器来组成一个堆栈
在这里插入图片描述
R 0 R_0 R0的数据被读取之后,SP就会指向 R 1 R_1 R1,依次类推。下图是王道PPT里面给出的一个加法实现的例子:
在这里插入图片描述
首先使用一次堆栈寻址,把栈顶元素送给ACC,此时SP同步加1。之后再使用一次堆栈寻址,把栈顶元素再送入到X中,SP同步再加1。最后是正常的直接寻址,把数据赋予Y。但是在执行时,我们为了方便连续的计算,比如你要算12+13+17这样的操作,你肯定是在计算器上把12和13相加,获得的25再和17相加。如果我们把加出来的这个25的值,放到原来13的位置(13被POP以后就没了),就可以实现继续加17 的操作,所以还需要一条压栈指令,这时候也很简单,让SP-1然后把数据存进去即可。所以完整过程是这样的:
在这里插入图片描述
所以出栈指令执行完成以后,SP自动加1,压栈指令执行前,SP自动减1,再压栈。

需要注意的是,考试的时候,必须看清楚栈顶是在大的位置还是小的位置,因为位置不同,SP的加减操作是不一样的:大的方向,读一次SP就减一次,压栈(入栈)前要先加一下,和上面的例子相反,考试考的就是你是否细心(不过一般也不会考,因为实际上计算机的SP是自动变化的)。
在这里插入图片描述
(2)软堆栈:
因为寄存器昂贵,存的也不多,所以如果涉及需要数据的连续计算时,可以进行软堆栈。软堆栈就是在主存里面划分一段区域来作为堆栈。容量更大,但是读取速度没有硬堆栈快(因为要访存)。
在这里插入图片描述
当然,其他的主要原理都是一样的。现在其实主要都是软堆栈实现,包括函数调用等,因为现在主存的速度也没有多慢,空间大成本低,多好。

3.10 总结一下十种寻址方法

在这里插入图片描述

现在已经把本章最重要的东西都学完了,后面第二部分将补充一点汇编的知识,上面这个十种寻址方式的图十分重要,如果你不能看着这个图准确的说出各个寻址方式的特点是什么,强烈建议你再回头全部看一遍。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值