汇编语言-第三章 寄存器(栈存储)

3.6 栈

栈是一种具有特殊的访问方式的存储空间。 栈有两个最基本的操作:入栈和出栈。 栈的操作规则为:LIFO(后进先出,Last In First Out)

3.7 CPU提供的栈机制

现今的CPU中都有栈的设计。 8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用。 8086CPU的入栈和出栈操作都是以字为单位进行的。 在内存中划分一块出来当作栈,这就是栈的本质。 CPU如何知道栈顶的具体位置?显然,也应该有相应的寄存器来存放栈顶的地址。 8086寄存器中有两个寄存器:段寄存器SS寄存器SP。 栈顶的段地址存放在SS中,偏移地址存放在SP中。在任意时刻,SS:SP指向栈顶元素。 push指令和pop指令执行时,CPU从SS和SP得到栈顶的地址。

push ax的执行,由下面两步之行:

  • SP=SP-2,SS:SP指向的内存单元处,以当前栈顶前面的单元为新的栈顶
  • 将ax中的内容送入SS:SP指向的内存单元出,SS:SP此时指向新栈顶。

pop ax 的执行过程和push ax相反,由以下两步完成:

  • 将SS:SP指向的内存单元处的数据送入ax中
  • SP=SP+2.SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

3.8栈顶超界问题

栈顶越界是超级危险的。我们将一段内存空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。 但是,由于我们在入栈、出栈时的不小心,而将这些数据、代码意外的改写,将会引发一连串的错误。 8086CPU并不保证我们对栈的操作不会越界。也就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道程序员安排的栈空间有多大。这就好比,CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道读者要执行的指令由多少。 所以我们在编程的时候要自己考虑栈顶越界的问题,要根据可能用到的最大栈空间发,来安排栈的大小,防止入栈的数据太多而导致的越界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的越界。

3.9 push、pop指令

栈空间也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。

push和pop指令的格式可以是如下形式:

push 寄存器    //将一个寄存器中的数据入栈    
pop  寄存器      //用一个寄存器来接收出栈的数据    

还可以是如下的形式:

push 段寄存器    // 将一个段寄存器中的数据入栈
pop  段寄存器      //用一个段寄存器接收出栈的数据

push和pop也可以在内存单元和内存单元之间传送数据,我们可以:

push 内存单元     //将一个内存字单元处的字入栈(注意,栈操作都是以字为单位)
pop  内存单元      //用一个内存字单元接收出栈的数据

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

例子

mov ax,1000H
mov ds,ax      //内存单元的段地址要放在ds中
push [0]          //将1000:0处的字压入栈中
pop [2]          //出栈的数据送入1000:2处

例子

将10000H~1000FH这段空间当做栈,初始状态是空的,将AX、BX、DS中的数据入栈。

mov ax,1000H
mov ss,ax      //设置栈的段地址,SS=1000H,不能直接向段寄存器SS送入数据,所以用AX中转
mov sp,0100H    //设置栈顶的偏移地址,因为栈空, 所以sp=0010H。
push ax
push bx
push ds

例子

编程: (1)将10000H~1000FH这段空间当做栈,初始状态栈是空的 (2)设置AX=001AH,BX=001BH (3)将AX、BX中的数据入栈 (4)然后将AX、BX清零 (5)从栈中恢复AX、BX原来的内容

mov ax,1000H
mov ss,ax
mov sp,0010H      //初始化栈顶
mov ax,001AH
mov bx,001BH
push ax
push bx
sub ax, ax        //将ax清零,也可以用mov ax,0
                       //sub ax,bx 的机器码为两个字节
                         //mov ax,0的机器码为3个字节
sub bx,bx
pop bx             //从栈中恢复ax、bx原来的数据,当前栈顶的内容是bx
pop ax

例子

(1)将10000H~1000FH这段空间当做栈,初始状态栈是空的 (2)设置AX=002AH,BX为002BH (3)利用栈,交换AX和BX中的数据

mov ax,1000H
mov ss,ax
mov sp 0010H
mov ax,002AH
mov bx,002BH
push ax
push bx
pop ax
pop bx

在10000H处写入字型数据2266H,可以用以下的代码完成:

mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax

或者用下面的方法: 说明 : 入栈的执行过程是: 1.先将记录栈顶偏移地址的SP寄存器中的内容减2,使得SS:SP指向新的栈顶单元 2.再将寄存器中的数据送入SS:SP指向的内存单元处,即10000H处

mov ax,1000H
mov ss,ax
mov sp,0002H
mov ax,2266H
push ax

push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,push和pop还要改变SP中的内容。 push和pop指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送。 执行push、pop指令需要两步操作。 执行push时,CPU的两步操作是: 1.先改变SP 2.向SS:SP处传送 执行pop时,CPU的两步操作是: 先读取SS:SP处的数据 后改变SP

注意: push、pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为:0~FFFFH SS、SP指示栈顶:改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。这就是8086CPU提供的栈操作机制。

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

  • 在SS、SP中存放栈顶的段地址和偏移地址
  • 提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。 (2)push指令的执行步骤:
  • SP=SP-2
  • 向SS:SP指向的字单元中送入数据
  • POP指令的执行步骤:
  • 从SS:SP指向的字单元中读取数据
  • SP=SP+2 (4)任意时刻,SS:SP指向栈顶元素 (5)8086CPU只记录栈顶,栈空间的大小要我们自己管理 (6)用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。 (7)push、pop实质上是一种内存传送指令

栈段

对于8086CPU,在编程时,我们可以根据需要,将一组内存单元定义为一个段。 将一段内存当做栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop等栈操作指令时就自动地将我们定义的栈段当做栈空间来访问。

一个栈段最大设置为多少?为什么? 最大容量为64KB push、pop等指令在执行的时候只修改SP,所以栈顶的变化范围是0~FFFFH,从栈空的时候SP=0,一直压栈,直到栈满的时候SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。

段的综述 我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元,这完全是我们自己的安排。

  • 我们可以用一个段存放数据,将它定义为“数据段”
  • 我们可以用一个段存放代码,将它定义为“代码段”
  • 我们可以用一个段当做栈,将它定义为“栈段” 我们可以这样安排,但若要想让CPU按照我们的安排来访问这些段,就要:
  • 对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问。
  • 对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令
  • 对于栈段,将它的地址放在SS中,将栈顶单元的偏移地址放在SP中国年,这样CPU在需要进行栈操作的时候,比如执行push、pop等指令,就将我们定义的栈段当做栈空间来使用。 CPU将内存中的某段内容当做代码,是因为CS:IP指向了那里;CPU将某内存当做栈,是因为SS:SP指向了那里。 一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是,关键在于CPU中寄存器的设置,即:CS,IP,SS,SP,DS的指向。

例子

我们将10000H~1001H安排为代码段,并在里面存储如下代码:

mov ax,1000H
mov SS,ax
mov SP,0020H
mov ax,cs
mov ds,ax
mov ax[0]
mov ax [2]
mov bx[4]
mov bx[6]
push ax
push bx
pop ax
pop bx

设置CS=1000H,IP=0,这段代码将得到执行。在这段代码中,我们又将10000H~1001FH安排为栈段和数据段。10000H~1001F这段内存,既是代码段,又是栈段和数据段。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏眯眯眼猫头鹰的小树杈

猫头鹰的深夜翻译:理解java的classloader

Java ClassLoader是java运行系统中一个至关重要但是经常被忽略的组件。它负责在运行时寻找并加载类文件。创建自定义的ClassLoader可以彻底...

1424
来自专栏瓜大三哥

Verilog

Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM型存储器,ROM存储器和reg文件。数组中的每一个单元通过一个数组索引进行寻址。...

25510
来自专栏游戏杂谈

JavaScript正则表达式的零宽断言

有类似如下的应用场景,一个全为数字的字符串,现在要将它每三位使用“,”进行分隔。例如:1099795448 –> 1,099,795,448。这里就可以使用正则...

1404
来自专栏大闲人柴毛毛

提高Java代码质量的Eclipse插件之Checkstyle的使用详解

CheckStyle是SourceForge下的一个项目,提供了一个帮助JAVA开发人员遵守某些编码规范的工具。它能够自动化代码规范检查过程,从而使得开发人员...

4309
来自专栏逍遥剑客的游戏开发

UE4学习笔记: Properties

2989
来自专栏Java技术栈

JVM运行时区域详解。

我们知道的JVM内存区域有:堆和栈,这是一种泛的分法,也是按运行时区域的一种分法,堆是所有线程共享的一块区域,而栈是线程隔离的,每个线程互不共享。 线程不共享区...

3064
来自专栏WindCoder

JVM-Java内存区域

JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,都有着各自的用途以及创建和销毁时间。包括以下几个如图所示的运行时数据区域:

2271
来自专栏java技术学习之道

JVM初探 -JVM内存模型

1624
来自专栏java一日一条

Java并发编程之原子操作类

当更新一个变量的时候,多出现数据争用的时候可能出现所意想不到的情况。这时的一般策略是使用synchronized解决,因为synchronized能够保证多个线...

841
来自专栏菩提树下的杨过

ZooKeeper 笔记(2) 监听数据变化

ZK中的每个节点都可以存储一些轻量级的数据,这些数据的变化会同步到集群中的其它机器。在应用中程序员可以添加watcher来监听这些数据的变化,watcher只会...

2597

扫码关注云+社区

领取腾讯云代金券