一、棧的基本概念
棧有兩個基本的操作:入棧和出棧。
入棧:將一個新的元素放到棧頂;
出棧:從棧頂取出一個元素。
棧頂的元素總是最后入棧,需要出棧時,又最先被從棧中取出。
棧的操作規則:后進先出
8086CPU提供的棧機制
8086CPU提供入棧和出棧指令: (最基本的)
PUSH(入棧)
POP (出棧)
push ax:將寄存器ax中的數據送入棧中;
pop ax :從棧頂取出數據送入ax。
push和pop指令也可以在內存和寄存器傳輸數據(以棧的形式)
8086CPU的入棧和出棧操作都是以字為單位進行的。
示例:
假設將10000H~1000FH這段內存當作棧來使用(其實CPU並不知道這段是代碼段,數據段還是棧段,都是人為設定的) 下面一段指令的執行過程: mov ax,0123H # AX=0123H push ax # 將AX的值推入棧中 mov bx,2266H # BX=2266H push bx # 將BX的值推入棧中 mov cx,1122H # CX=1122H push cx # 將CX的值推入棧中 pop ax # 將棧頂的2個內存單元取出放到AX寄存器中,AX=1122H pop bx # 將棧頂的2個內存單元取出放到BX寄存器中,BX=2266H pop cx # 將棧頂的2個內存單元取出放到CX寄存器中,CX=0123H
那么問題來了,在執行push和pop的時候,如何知道哪個單元是棧頂單元?
8086CPU中,有兩個寄存器:
- 段寄存器SS 存放棧頂的段地址
- 寄存器SP 存放棧頂的偏移地址
任意時刻,SS:SP指向棧頂元素。
二、push和pop指令
push 指令的執行過程
在執行push ax指令時,主要做了一下2件事
(1)先將SP=SP–2
(2)將ax中的內容送入SS:SP指向的內存單元處,SS:SP此時指向新棧頂。
pop 指令的執行過程
在執行pop ax指令時,主要做了一下2件事
(1)先將SS:SP指向的內存單元處的數據送入ax中;
(2)SP = SP+2,SS:SP指向當前棧頂下面的單元,以當前棧頂下面的單元為新的棧頂。
當棧為空的時候,SS:SP指向最高地址空間的下一位
任意時刻,SS:SP 指向棧頂元素,當棧為空的時候,棧中沒有元素,也就不存在棧頂元素,所以SS:SP 只能指向棧的最底部單元下面的單元,該單元的偏移地址為棧最底部的字單元的偏移地址+2,棧最底部字單元的地址為1000:000E,所以棧空時,SP=0010H。
棧頂超界的問題(簡單了解就好,我們又不做黑客)
原因:SS和SP只記錄了棧頂的地址,依靠SS和SP可以保證在入棧和出棧時找到棧頂。
上面我說了,CPU並不知道這段是代碼段,數據段還是棧段,都是人為設定的所以加入這個棧空了,我們繼續pop,它就會把其它段的數據pop出。所以非常危險
解決辦法:一個很NC的辦法,就是我們在編程的時候要自己操心棧頂超界的問題 ,要根據可能用到的最大棧空間,來安排棧的大小,防止入棧的數據太多而導致的超界;執行出棧操作的時候也要注意,以防棧空的時候繼續出棧而導致的超界。
push和pop指令具體可以操作
# 可以為通用寄存器賦值 push ax pop bx # 可以為段寄存器賦值 push ds pop es # 可以為內存單元賦值 push [0] pop [2]
示例:
1、將10000H~1000FH 這段空間當作棧,初始狀態是空的,將 AX、BX、DS中的數據入棧。
2、編程
(1)將10000H~1000FH 這段空間當作棧,初始狀態是空的;
(2)設置AX=001AH,BX=001BH;
(3)將AX、BX中的數據入棧;
(4)然后將AX、BX清零;
(5)從棧中恢復AX、BX原來的內容。
3、編程:
(1)將10000H~1000FH 這段空間當作棧,初始狀態是空的;
(2)設置AX=002AH,BX=002BH;
(3)利用棧 ,交換 AX 和 BX 中的數據。
4、補全下面的代碼,完成同樣的功能:在10000H處寫入字型數據2266H。
__________
__________
__________
mov ax,2266H
push ax
mov ax,1000H mov ss,ax mov sp,2 # 題目要求的是要將10000H位置寫入字形數據,10000H的偏移量是0,在執行push操作的時候回將sp-2,所以我們要把它指向10002H的位置 mov ax,2266H push ax
棧段
上面說過我們可以定義一段內存單元為代碼段,數據段或棧段。
我們可以將一個段空間當做一個棧段,一個棧段最大64K,因為16位CPU的偏移地址最大為2^16B,如果超出FFFF它會重新變成0(FFFF+1=0000,1被拋棄了)。
CPU是如何判斷哪塊是什么段的?
我們可以將一段內存定義為一個段,用一個段地址指示段,用偏移地址訪問段內的單元。這完全是我們自己的安排。
我們可以用一個段存放數據,將它定義為“數據段”;
我們可以用一個段存放代碼,將它定義為“代碼段”;
我們可以用一個段當作棧,將它定義為“棧段”;
我們可以這樣安排,但若要讓CPU按照我們的安排來訪問這些段,就要:
對於數據段,將它的段地址放在 DS中,用mov、add、sub等訪問內存單元的指令時,CPU就將我們定義的數據段中的內容當作數據段來訪問;
對於代碼段,將它的段地址放在 CS中,將段中第一條指令的偏移地址放在IP中,這樣CPU就將執行我們定義的代碼段中的指令;
對於棧段,將它的段地址放在SS中,將棧頂單元的偏移地置放在 SP 中,這樣CPU在需要進行棧操作的時候,比如執行 push、pop 指令等,就將我們定義的棧段當作棧空間來用。
可見,不管我們如何安排 ,CPU 將內存中的某段內存當作代碼 ,是因為CS:IP指向了那里;CPU將某段內存當作棧 ,是因為 SS:IP 指向了那里。