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