汇编(五)栈、CPU提供的栈机制、push、pop指令

3.6 栈

  • 栈是一种具有特殊的访问方式的存储空间,他的特殊性就在于 最后一个进入这个空间的数据,是最先出去的
  • 栈有两种基本的操作:入栈和出栈

      1. 入栈:将一个新的元素放到栈顶
      2. 出栈:从栈顶取出一个元素
    • 栈顶元素总是最后一个入栈的,需要出的时候,又会是第一个被取出的
    • 操作规则:LIFO

      • (Last in first out) 后进先出

3.7 CPU提供的栈机制

  • 现今的CPU都有栈的设计, CPU提供相关的指令来以栈的方式访问内存空间
  • 这就意味着,我们再CPU编程的时候,可将一段内存当做栈来使用

    push(入栈):

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

    pop(出栈):

    • pop ax:从栈顶取出数据送入ax中
    入栈和出栈的操作都是以 字 为单位进行的
  • 字型数据用两个单元存放,高地址单元放高8位,低地址单元房地8位
  • 汇编(五)栈、CPU提供的栈机制、push、pop指令
1. CPU如何知道一段内存空间被当做栈使用?
  • 有两个寄存器:

    • 段寄存器:SS 存放栈顶的段地址
    • 寄存器:SP 存放栈顶的偏移地址
  • 任意时刻 SS:SP指向栈顶元素
2. 执行push和pop的时候,如何知道那个单元是栈顶单元的?

汇编(五)栈、CPU提供的栈机制、push、pop指令

  • push ax
    1. 先进行:SP = SP - 2
    2. 然后将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新的栈顶

汇编(五)栈、CPU提供的栈机制、push、pop指令

  • pop ax
    1. 先将栈顶(SS:SP)数据拿出来给ax
    2. 然后:SP = SP + 2

知识点:

  • 当pop之后其实栈中的数据还是存在的但是并不属于栈中内内容了、不能操作、当下一次push的时候回将其进行覆盖,并没有一个真正的删除,和硬盘格式化之后还可以恢复是一个道理,只是改变的指针的指向
  • 如果将10000~1000F作为栈的空间:初始转态的栈是空的,此时:

    • ss=1000
    • sp=0010(F的高一位地址 )
    • 栈空间大小为16个字节,栈最底部的字节单元地址为 1000:000E
    • 任意时刻 SS:SP指向栈顶,当栈中只有一个元素的时候:

      **SS=1000,SP=000E**
    • 栈为空,就相当于栈中唯一的元素被pop,出栈后,SP=SP+2,SP原来为000E,加2后SP=10
    • 所以当栈为空的时候,SS=1000,SP=10
  • 换个角度说:
    1. 任意时刻,SS:SP指向栈顶元素,当栈为空时候,栈中没有元素,也就不存在栈顶元素
    2. 所以SS:SP只能指向栈的最底部单元下面的单元,该单元中的偏移地址为栈最底部的字节单元的偏移地址+2
    3. 栈最底部字单元的地址为1000:000E,所以栈为空时 SP=000E-2=0010
    4. 只有栈内有元素的时候才有栈顶,否则不存在栈顶

3. CPU如何知道当前要执行的指令所在的位置?
  • 寄存器cs和ip中存放着当前执行的段地址和偏移地址

3.8 栈顶超界的问题

汇编(五)栈、CPU提供的栈机制、push、pop指令

  • SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈是找到栈顶
  • 当栈满的时候再使用push指令入栈,栈空的时候在pop指令出栈都会发证栈顶超界的问题。 栈顶越界是危险的,因为会覆盖掉其他数据
  • 因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了一些具有其他用途的数据、代码等、这些数据、代码可能使我们自己程序中的、也有可能是别的程序中的、如果是系统关键内容就危险了
  • 但是由于我们在入栈时的不小心而将这些数据、代码意外的改写、都会引发一连串的错误。我么当然希望CPU可以帮我们解决这个问题
  • 可是,如何保证在入栈,出栈时,栈顶不会超出栈空间?

    • 其实CPU在执行的时候只考虑两种情况

      1. 当前栈顶在何处
      2. 当前执行的指令是那一条
    • 结论:

      • 我们再编程的时候要看自己操心栈的超界问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界了;
      • 执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致超界

3.9 push、pop指令

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

    栈与内存:
    • 栈空间当然也是内存空间的一部分,它是一段可以以一种特殊方式进行访问的内存空间
    • 栈只是一种数据结构的存放格式
  • push和pop的指令格式(1)

      1. push 寄存器:将一个寄存器的数据入栈
      2. pop 寄存器: 出栈,用一个寄存器接受出栈的数据
    • 如:

      • push ax
      • pop bx
  • push和pop的指令格式(2)

      1. push 段寄存器:将一个段寄存器的数据入栈
      2. pop 段寄存器:出栈, 用一个段寄存器接受出栈的数据
    • 如:

      • push ds
      • pop es
  • push和pop的指令格式(3)

      1. push 内存单元:将一对内存单元的数据入栈(栈操作都是以字为单位的)
      2. pop 内存单元:出栈, 用一对内存单元接受出栈的字单元数据
    • 如:

      • push [0]
      • pop [2]
  • 执行指令时,CPU要知道内存单元的地址,可以在push,pop指令中给出内存单元的偏移地址,段地址是在指令执行时,CPU从ds中自动获取的

    段寄存器都是以S结尾的,通用寄存器都是以X结尾的

    • 段地址在执行时可以从DS获得
    • 数据的段地址永远是从DS获得
    • 代码的段地址永远是从CS中获得
    • 栈的段地址永远是从SS中获得
  • 汇编(五)栈、CPU提供的栈机制、push、pop指令
  • # 设置栈的段地址,ss=1000,不能直接向段寄存器送入数据,要通过ax通用寄存器传递
    mov ax, 1000
    mov ss, ax
    # 设置栈顶的偏移地址,因为栈为空,所以sp=0010
    mov sp, 0010
    # 压入数据
    push ax
    push bx
    push ds
  • 汇编(五)栈、CPU提供的栈机制、push、pop指令
  • mov ax, 1000
    mov ss, ax
    mov sp, 0100
    mov ax=001a
    mov bx=001b
    push ax
    push bx
    sub ax, ax
    sub bx, bx
    pop bx
    pop ax
  • 从上面的程序可以看到,用栈来暂存以后需要恢复的寄存器中的内存时,出栈的顺序要和入栈的顺序相反, 因为最后入栈的寄存器的内容在栈顶,所以在恢复时,是最先出栈的
  • 汇编(五)栈、CPU提供的栈机制、push、pop指令
  • mov bx, 1000
    mov ss, bx
    mov ap, 10
    mov ax, 002a
    mov bx, 002b
    push ax
    push bx
    pop ax
    pop bx
  • 汇编(五)栈、CPU提供的栈机制、push、pop指令
  • mov ax, 1000
    mov ss, ax
    mov sp, 2
    mov ax, 226
    push, ax
  • Pop、和push实际上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是 push和pop指令访问的内存单元的地址不是在指令中给出,而是ss:p指出的
  • 我们需要是十分清楚的是,push和pop指令同mov指令不同, CPU执行mov指令只需要一步, 而执行push、pop指令却需要两步操作
  • 执行push时:

    • 先改变sp,后向ss:sp压入
  • 执行pop时:

    • 先读取ss:sp处的数据, 后改变sp
  • push、pop指令修改的只是sp,也就是说栈顶的变化范围最大为:0~FFFF(SP寄存器的大小)
  1. 在ss:sp中存放栈顶的段地址和偏移地址, 提供入栈和出栈指令, 他们根据ss:sp指示的地址, 按照栈的方式访问内存单元
  2. 任意时刻。ss:sp执行栈顶元素
  3. 8086CPU只记录栈顶, 栈空间的大小我们要自己管理
  4. 用栈来暂存以后需要恢复的寄存器的内容, 寄存器出栈的顺序要和 入栈相反

    • 栈是一种非常重要的机制, 一定要深入理解, 灵活掌握
上一篇:怎样从`MacOSX` 上完全卸载`Nodejs` ?


下一篇:check sha256sum value consistent with the reference value