3.6 栈
在这里,我们对栈的研究仅限于这个角度:栈是一种具有特殊的访问方式的存储空间,它的特殊性就在于,最后进入这个空间的数据,最先出去。
可以用一个盒子和3本书来描述栈的操作方式。
一个开口的盒子就可以看成一个栈空间,现在就有3本书,《高等数学》、《C语言》、《软件工程》,把它们放到盒子中,操作的过程如图3.7所示。
现在的问题是,一次只允许取一本,我们如何将3本书从盒子中取出来?
显然,必须从盒子的最上边取。这样取出的顺序就是:《软件工程》、《C语言》、《高等数学》,和放入的顺序相反,如图3.8所示。
3.8
从程序化的角度来讲,应该有一个标记,这个标记一直指示盒子最上边的书。
如果说,上例中的盒子就是一个栈,我们可以看出,栈有两个基础的操作:入栈和出栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)。
3.7 CPU提供的栈机制
现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,在基础8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈的指令,最基本的两个是PUSH(入栈)和POP(出栈)。比如push ax表示将寄存器ax中的数据送入栈中,pop ax表示从栈顶取出数据送入ax。8086CPU的入栈和出栈操作都是以字为单位进行的。
下面举例说明,我们将10000H~1000FH这段内存当作栈来使用。
图3.9描述了下面一段指令的执行过程。
mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax
pop bx
pop cx
注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位。
读者看到图3.9所描述的push和pop指令的执行过程,是否有一些疑惑?总结一下,大概是这两个问题。
其一,我们将10000H~1000FH这段内存当作栈来使用,CPU执行push和pop指令时,将对这段空间按照栈的后进先出的规则进行访问。但是,一个重要的问题是,CPU如何知道10000H~1000FH这段空间被当作栈来使用?
其二,push ax 等入栈指令执行时,要将寄存器中的内容放入当前栈顶单元的上方,称为新的栈顶元素;pop ax 等指令执行时,要从栈顶单元中取出数据,送入寄存器中。显然,push、pop在执行的时候,必须知道哪个单元是栈顶单元,可以,如何知道呢?
这不禁让我们想起另外一个讨论过的问题,就是,CPU如何知道当前要执行的指令所在的位置?我们现在知道答案,那就是CS、IP中存放着当前指令的段地址和偏移地址。现在的问题是:CPU如何知道栈顶的位置?显然,也应该用相应的寄存器来存放栈顶的地址,8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。**任何时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。
现在,我们可以完整地描述push和pop指令的功能了,例如push ax。
push ax的执行,由以下两步完成。
(1) SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2) 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新的栈顶。
图3.10描述了8086CPU对push指令的执行过程。
从图中我们可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。
问题 3.6
如果将10000H~1000FH这段空间当作栈,初始状态是空的,此时,SS=1000H,SP= ?思考后看分析。
分析:
SP=0010H,如图3.11所示。
将10000H~1000FH这段空间当作栈段,SS=1000H,栈空间大小为16字节,栈最低部的字单元地址为1000:000E。任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=00EH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP-2,SP原来为000EH,加2后SP=10H,所以,当栈为空的时候,SS=1000H,SP=10H。
换一个角度,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H。
接下来,我们描述POP指令的功能,例如pop ax。
pop ax 的执行过程和push ax刚好相反,由以下两步完成。
(1)将SS:SP指向的内存单元处的数据送入ax中。
(2)SP=SP+2,SS:SP指向当前栈顶下面的元素,以当前栈顶下面的单元为新的栈顶。
图3.12描述了8086CPU对POP指令的执行过程,
注意,图3.12中,出栈后,SS:SP指向新的栈顶1000EH,pop操作前的栈顶元素,1000CH处的2266H依然存在,但是,它已不在栈中。当再次执行push后等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。