• 第三章 寄存器(内存访问)其二


    3.6  栈

    我们研究栈的角度:

    栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。

    栈有两个基本的操作:入栈和出栈

    入栈:将一个新的元素放到栈顶

    出栈:从栈顶取出一个元素

     

    栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。

    栈的操作规则:LIFO(后进先出)

    3.7  CPU提供的栈机制

    现今的CPU中都有栈的设计。

    8086CPU提供相关的指令来以栈的方式访问内存空间。

    这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

    8086CPU提供入栈和出栈指令:(最基本的)

    PUSH(入栈)

    POP(出栈)

    push  ax:将寄存器ax中的数据送入栈中;

    pop   ax: 从栈顶取出数据送入ax。

    8086CPU的入栈和出栈操作都是以字为单位进行的。

    SS:存放当前栈顶的段地址

    SP:存放当前栈顶的偏移地址

    任意时刻,SS:SP指向栈顶元素。

    push指令的执行过程

    push  ax

    1)SP=SP-2;

    2)将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

    在初始状态,栈是空的,此时,SS:SP指向栈空间最高地址单元的下一个单元。

    POP指令的执行过程

    pop   ax

    1)将SS:SP指向的内存单元处的数据送入ax中;

    2)SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

    注意:

    出栈后,SS:SP指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是,它已不在栈中。

    当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。

    3.8  栈顶超界的问题

    SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。

    可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?

    当栈满的时候再使用push指令入栈,栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。

    栈顶超界是危险的。

    因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。

    但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。

    我们当然希望CPU可以帮我们解决这个问题。

    比如说在CPU中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU在执行push指令的时候靠检测栈顶上限寄存器,在执行pop指令的时候靠检测栈顶下限寄存器保证不会超界。

    实际情况:8086CPU中并没有这样的寄存器。

    8086CPU不保证对栈的操作不会超界。

    这就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。这点就好像,CPU只知道当前要执行的指令在何处(由CS:IP指示)而不知道读者要执行的指令有多少。

    由这些可以得出:

    8086CPU的工作机理,只考虑当前的情况:

    当前栈顶在何处;

    当前要执行的指令是哪一条

    3.9push、pop指令

    push和pop指令是可以在寄存器和内存之间传送数据的。

    push和pop指令的格式(1)

    push   寄存器:将一个寄存器中的数据入栈

    pop寄存器:出栈,用一个寄存器接受出栈的数据。

    例如:

    push  ax

    pop   bx

    push和pop指令的格式(2)

    push   段寄存器:将一个段寄存器中的数据入栈

    pop段寄存器:出栈,用一个段寄存器接受出栈的数据。

    例如:

    push  ds

    pop  es

    push和pop指令的格式(3)

    push  内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)

    pop   内存单元:出栈,用一个内存字单元接受出栈的数据。

    例如:

    push   [0]

    pop   [2]

    指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。

     

     

     

     

     

     

     

     

     push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。

    同时,push和pop指令还要改变SP中的内容。

    我们要十分清楚的是,push和pop指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送,而执行push、pop指令却需要两步操作。

    执行push时:

    先改变SP,后向SS:SP处传送。

    执行pop时:

    先读取SS:SP处的数据,后改变SP。

    注意:

    push、pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH。

    提供:SS、SP指示栈顶;改变SP后写内存的入栈指令;都内存后改变SP的出栈指令。

    这就是8086CPU提供的栈操作机制。

    栈的综述

    1)8086CPU提供了栈操作机制,方案如下:

    在SS,SP中存放栈顶的段地址和偏移地址;提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元。

    2)push指令的执行步骤;

      1、SP=SP-2;

      2、向SS:SP指向的字单元中送入数据。

    3)pop指令的执行步骤

      1、从SS:SP指向的字单元中读取数据;

      2、SP=SP+2

    4)任意时刻,SS:SP指向栈顶元素。

    5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。

    6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。

    7)push、pop实质上是一种内存传送指令,注意他们的灵活应用。

    3.10  栈段

    前面讲过,对于8086PC机,在编程时,我们可以根据需要,将一组内存单元定义为一个段。

    我们可以将长度为N(N≤64K)的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段。

    将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop等栈操作指令时就自动地将我们定义地栈段当作栈空间来访问。

    如何使得如push、pop等栈操作指令访问我们定义地栈段呢?

    将SS:SP指向我们定义地栈段。

    问题3.11

    如果我们将10000H~1FFFFH这段空间当作栈段,初始状态是空的,此时,SS=1000H,SP=?

    解:

    我们将10000H~1FFFFH这段空间当作栈段,SS=1000H,栈空间大小为64KB,栈最底部的字单元地址为1000:FFFE。

    任意时刻,SS:SP指向栈顶,当栈中只有一

    个元素的时候,SS=1000H,SP=FFFEH。

    栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2.

    SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0.

    换个角度看

    任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:FFFE,所以栈为空时,SP=0000H。

    问题3.12

    一个栈段最大可以设为多少?为什么?

    首先从栈操作指令所完成的功能的角度上来看,push、pop等指令在执行的时候只修改SP;

    所以栈顶的变化范围是0~FFFFH,从栈空的时候的SP=0,一直压栈,直到栈满时SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。

    所以一个栈段的容量最大为64KB。

    段的综述

    我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。

    我们可以用一个段存放数据,将它定义为数据段

    我们可以用一个段存放代码,将它定义为代码段

    我们可以用一个段当作栈,将它定义为栈段。

    我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:

    对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段来访问。

    对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令。

    对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。

    监测点3.2

     

  • 相关阅读:
    Qt通用方法及类库9
    设计模式原则(7)--Composition&AggregationPrinciple(CARP)--合成&聚合复用原则
    设计模式原则(6)--Open-Closed Principle(OCP)--开闭原则
    鼠标悬停出现页面
    设计模式(23)--Visitor--访问者模式--行为型
    设计模式(22)--Template Method(模板方法模式)--行为型
    设计模式(21)--Strategy(策略模式)--行为型
    设计模式(20)--State(状态模式)--行为型
    设计模式(19)--Observer(观察者模式)--行为型
    设计模式(18)--Memento(备忘录模式)--行为型
  • 原文地址:https://www.cnblogs.com/fate-/p/12856038.html
Copyright © 2020-2023  润新知