小白的汇编之路 (三)下
前言
这一部分将要涉及到栈,假设读者对栈有一定的认识。
栈
栈是一种很有用的“工具”
它和队列区别在于队列是先进先出,栈是先进后出
CPU中的栈机制
在前面我们学过,栈也是一种段,它的段地址存在SS寄存器中,偏移地址存在SP寄存器中,SP我们可以理解为栈顶指针
8086CPU中对栈的操作,例如压栈(PUSH),出栈(POP)都是以字为单位的。
如下图:(表格中黄色部分为栈顶指针所指的内存单元)
假设我们把1001:0000H~1001:000AH这段内存空间作为栈,且其中每个内存单元都是空的,此时栈顶指针指的是栈底(1001:000BH)(此时对应栈0),则此时有SS:1001H,SP:000BH
地址 | 栈0 | 栈1 | 栈2 | 栈3 |
---|---|---|---|---|
1000FH | ? | ? | 12H | 12H |
10010H | 8EH | 8EH | ||
10011H | 00H | 00H | ||
10012H | 00H | 00H | ||
10013H | 00H | 00H | ||
10014H | 00H | 00H | ||
10015H | 00H | 00H | ||
10016H | 00H | 00H | ||
10017H | 00H | 00H | ||
10018H | 00H | 00H | ||
10019H | 23H | 23H | 23H | |
1001AH | 01H | 01H | 01H | |
1001BH | ? | ? | ? | ? |
1001CH | ? | ? | ? | ? |
图A
压栈
对这个栈进行压栈操作,把0123H存入AX寄存器中,再把AX压栈。
代码为:
mov ax,0123H
push ax //将ax寄存器的值压入栈中
(此时对应栈1)
压入后,此时SP=SP-2(栈顶指针偏移)
SS:1001H,SP:0009H
越界
若我们此时向栈中压入5个数据,分别是,0000H,0000H,0000H,0000H,8E12H,(此时对应栈2)代码为:
push 0000H
push 0000H
push 0000H
push 0000H
push 8E12H
此时SS:1000H,SP:000FH
在这一步你或许会有疑惑:我们明明定义10010H~1001AH为栈空间,但是对不在栈空间的内存单元(1000FH)赋了值,这样不会报错么?
事实上8086CPU是不保证使用者对栈的操作不会超界; CPU只关心当前的栈顶在哪里?要执行的指令是什么?
CPU并不知道使用者定义的栈空间的大小;
换句话说就是,对栈的操作是否越界只能由使用者自行管理。
而且由于越界操作可能会对某些存储重要数据的内存单元进行操作,极有可能造成重要数据的破坏,所以这是必须避免的!
出栈
若我们想要将栈顶指针所指的内存单元数据存入AX寄存器中?该怎么办?
pop ax //将栈顶指针所指的内存单元赋给ax寄存器
(此时对应栈3)
SS:1001H,SP:0001H
PUSH和POP
push和pop实际上是一种内存传送指令,不同于mov指令,push和pop不需要在指令中指明数据存放的位置;push和pop依赖于栈顶指针sp的移动。
由图A不难看出:
当push一个数据进栈时,sp=sp-2;
当pop数据时,sp=sp+2。
栈的大小
之前提过段的大小为64KB,栈也是相同的原理,不过是受SP的影响。