3.6-3.7

栈的规则是LIFO(last in first out)

也就是先进去的就到了栈底,最后放进去的就在表面上,最后放进去的可以最先拿出来。

8086提供入栈和出栈指令,PUSH和POP

image-20220316110936211

以-我们的一段内存可以以栈的方式来访问。执行的时候

1
2
mov ax,0123
push ax

即将寄存器里的数据放到栈中,因为是字型数据,占高八位和第八位,栈底对应的是高地址位,所以把高位01先入栈,到栈底。

向上堆叠时

1
2
mov bx,2266
push bx

会以同样形式向上走一个字型内存。

image-20220316111225666

出栈时用pop先从栈顶出

1
2
3
4
5
6
pop ax
执行后:
ax = 1122,出栈入寄存器的意思。
然后我们再将剩下两个栈单元也出来,赋值给bx寄存器
pop bx,执行后
bx=2266

字型数据用两个单元存放,高地址存放高八位,反之亦然。

CPU如何找到栈顶?

有相应的寄存器来存放栈顶的地址,段寄存器SS和寄存器SP,栈的段地址放在SS中,偏移地址放在SP中。任意时刻,SS:SP指向栈顶元素。push和pop执行时,CPU从SS:SP获得栈顶地址。

image-20220316113831264

当前属性

1
2
3
4
5
6
7
8
9
10
SS=1000
SP=000E则指向地址1000E为此时栈顶地址
AX=2266然后开始入栈
要先把栈顶指针向上移动两个,腾出位置,然后才能入栈
SP=SP-2
SP=000C此时
栈顶指针指向1000C继续入栈
push ax
完成。

image-20220316114825392

如果初始栈是空的,怎么办?栈顶指针会指向栈空间最高地址的下一个单元,很奇妙。

执行 push ax之后,SS:SP指向栈中第一个元素

image-20220316115200484

我们来看POP执行过程,先

1
2
3
4
当我们按下pop ax时,CPU会先把SS:SP指向内存单元处的数据送入ax,
即ax = 2266
然后SP = SP + 2
栈顶指针向下移动了两个格

注意!pop操作前的栈顶元素在那个内存单元中2266仍然存在,不会消失!但是我们说他已经不在栈中,当再次入栈时,新数据会直接覆盖他!

3.8-栈顶超界问题

如何保证出栈入栈时栈顶不会超出栈空间?

image-20220316115656446

栈空时指向栈最高地址的下一位,如图。如果栈空间有16字节大小,则执行八次 push ax后,栈空间满,再次执行push

ax中的数据会送入1000E,将栈外空间覆盖!

image-20220316115838875

同理执行八次pop ax后从栈中弹出8个字,栈空,SS:SP指向10020

再次执行pop ax: SP=SP + 2指向10022超出了栈空间,也覆盖到了外面。

注:8086CPU没有解决这些问题,需要我们自己计算,自己考虑,因为栈外空间很可能存了其他内存数据,被改写可能会引起错误!

push,pop一般传参寄存器,但是也可以直接到内存空间等,以下方式都行:

image-20220316120751984

比如:

1
2
3
4
mov ax,1000
mov ds,ax先传送内存单元的段地址
push [0]将1000:0000处的字数据压入栈
pop [2]出栈到1000:0002处

可以将段寄存器的数据入栈,也可以出栈到段寄存器

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

下面练习:

image-20220316121711836

image-20220316121846786

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,1000
mov ss,ax
mov sp,0010因为初始栈空要指向栈最高地址的下一个
mov ax,001a
mov bx 001b
push ax
push bx
然后清零
mov ax,0
mov bx,0
pop bx
pop ax注意因为ax先入栈,所以在下面,最后出,出栈时,先出来的应该是bx

image-20220316122013524

看这个题,如果是交换AX,BX中的数据则出栈时先出给AX即可

1
2
3
4
5
6
7
8
9
10
mov ax,1000
mov ss,ax
mov bx,0010
mov sp,bx
mov ax,0
mov bx,0
push ax
push bx
pop ax即将原来bx中的数据出栈到ax,形成交换
pop bx

image-20220316122236564

再来看此题:

image-20220316122348125

显然我们栈空时向下进位直接为0000了。

从栈空时SP=0000到栈满时SP=0000,如果再次压栈,将覆盖最初的数据所以说最大容量为(2**4)^4也就是16^4为64KB,即一段的总空间,偏移地址的变化范围。

注意一下知识点:

image-20220316122635947

数据段的段地址:DS,用mov,add等指令访问时,里面的内容当作数据来访问。

代码段的段地址:CS,里面的内容当作指令的地址。

栈段的段地址:SS

例:我们将10000-1001f安排为代码段,并且储存以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov ax,1000
mov ss,ax因为不能直接向ss中传递数据
mov sp,0020
初始化栈之后
mov ax,cs将段地址放到ax中然后放到ds中
mov ds,ax设置数据段地址,ds存放的就是指令的地址
mov ax,[0]找到cs指向的每一个数据
add ax,[2]
mov bx,[4]
add bx,[6]赋值到ax,bx之后再入栈
push ax
push bx
pop ax
pop bx再数据交换

设置cs=1000,ip=0,这段代码将得到执行,我们在代码中又将10000-1001f安排为栈段和数据段。

即一段内存既可以是代码的存储空间也可以是数据的存储空间。

image-20220316123440581

结束,下面第四章,编译运行exe