棧是一種具備特殊的訪問方式的存儲空間。
棧有兩個最基本的操做:入棧和出棧。
棧的操做規則爲:LIFO(後進先出,Last In First Out)程序員
現今的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相反,由如下兩步完成:code
- 將SS:SP指向的內存單元處的數據送入ax中
- SP=SP+2.SS:SP指向當前棧頂下面的單元,以當前棧頂下面的單元爲新的棧頂。
棧頂越界是超級危險的。咱們將一段內存空間安排爲棧,那麼在棧空間以外的空間裏極可能存放了具備其餘用途的數據、代碼等,這些數據、代碼多是咱們本身程序中的,也多是別的程序中的(畢竟一個計算機系統中並非只有咱們本身的程序在運行)。
可是,因爲咱們在入棧、出棧時的不當心,而將這些數據、代碼意外的改寫,將會引起一連串的錯誤。
8086CPU並不保證咱們對棧的操做不會越界。也就是說,8086CPU只知道棧頂在何處(由SS:SP指示),而不知道程序員安排的棧空間有多大。這就比如,CPU只知道當前要執行的指令在何處(由CS:IP指示),而不知道讀者要執行的指令由多少。
因此咱們在編程的時候要本身考慮棧頂越界的問題,要根據可能用到的最大棧空間發,來安排棧的大小,防止入棧的數據太多而致使的越界;執行出棧操做的時候也要注意,以防棧空的時候繼續出棧而致使的越界。內存
棧空間也是內存空間的一部分,它只是一段能夠以一種特殊的方式進行訪問的內存空間。ast
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 axpush、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這段內存,既是代碼段,又是棧段和數據段。