2022-12-08棧

棧是一種具有特殊的訪問(wèn)方式的存儲(chǔ)空間。它的特殊性就在于,最后進(jìn)入這個(gè)空間的數(shù)據(jù),最先出去。

可以用一個(gè)盒子和3本書(shū)來(lái)描述棧的這種操作方式。

一個(gè)開(kāi)口的盒子就可以看成一個(gè)棧空間,現(xiàn)在有3本書(shū),《高等數(shù)學(xué)》、《C語(yǔ)言》、《軟件工程》,把它們放到盒子中,操作的過(guò)程如圖所示。

現(xiàn)在的問(wèn)題是,一次只允許取一 本, 我們?nèi)绾螌?本書(shū)從盒子中取出來(lái)?

顯然,必須從盒子的最上邊取。這樣取出的順序就是:《軟件工程》 、《C 語(yǔ)言》、《高等數(shù)學(xué)》,和放入的順序相反,如圖3.8所示。

從程序化的角度來(lái)講,應(yīng)該有一個(gè)標(biāo)記,這個(gè)標(biāo)記一.直指示著盒子最上邊的書(shū)。

如果說(shuō),上例中的盒子就是一個(gè)棧,我們可以看出,棧有兩個(gè)基本的操作:入棧和出棧。入棧就是將一個(gè)新的元素放到棧頂,出棧就是從棧頂取出一個(gè) 元素。棧頂?shù)脑乜偸亲詈笕霔#枰鰲r(shí),又最先被從棧中取出。棧的這種操作規(guī)則被稱為: LIFO(Last In First Out,后進(jìn)先出)。

CPU提供的棧機(jī)制

現(xiàn)今的CPU中都有棧的設(shè)計(jì),8086CPU也不例外。8086CPU提供相關(guān)的指令來(lái)以棧的方式訪問(wèn)內(nèi)存空間。這意味著,在基于8086CPU編程的時(shí)候,可以將一段內(nèi)存當(dāng)作棧來(lái)使用。

8086CPU提供入棧和出棧指令,最基本的兩個(gè)是PUSH(入棧)和POP(出棧)。比如,push ax表示將寄存器ax中的數(shù)據(jù)送入棧中,pop ax表示從棧項(xiàng)取出數(shù)據(jù)送入ax。8086CPU的入棧和出棧操作都是以字為單位進(jìn)行的。

下面舉例說(shuō)明,我們可以將10000H~1000FH這段內(nèi)存當(dāng)作棧來(lái)使用。

mov ax, 0123H

push ax

mov bx, 2266H

push bx

mov cx, 1122H

push cx

pop ax

pop bx

pop сх

字型數(shù)據(jù)用兩個(gè)單元存放,高地址單元存放高8位,低地址單元存放低8位。

其一,我們將1000H~1000FH這段內(nèi)存當(dāng)作棧來(lái)使用,CPU執(zhí)行push和pop指令時(shí),將對(duì)這段空間按照棧的后進(jìn)先出的規(guī)則進(jìn)行訪問(wèn)。但是,一個(gè)重要的問(wèn)題是,CPU如何知道10000H~1000FH這段空間被當(dāng)作棧來(lái)使用?

其二,push ax等入棧指令執(zhí)行時(shí),要將寄存器中的內(nèi)容放入當(dāng)前棧頂單元的上方,成為新的棧頂元素; pop ax 等指令執(zhí)行時(shí),要從棧項(xiàng)單元中取出數(shù)據(jù),送入寄存器中。顯然,push、 pop 在執(zhí)行的時(shí)候,必須知道哪個(gè)單元是棧頂單元,如何知道呢?

CPU如何知道當(dāng)前要執(zhí)行的指令所在的位置?我們現(xiàn)在知道答案,那就是CS、IP中存放著當(dāng)前指令的段地址和偏移地址?,F(xiàn)在的問(wèn)題是: CPU如何知道棧頂?shù)奈恢?顯然,也應(yīng)該有相應(yīng)的寄存器來(lái)存放棧頂?shù)牡刂罚?086CPU 中,有兩個(gè)寄存器,段寄存器SS和寄存器SP,棧頂?shù)亩蔚刂反娣旁赟S中,偏移地址存放在SP中。任意時(shí)刻,SS:SP 指向棧頂元素。push 指令和pop指令執(zhí)行時(shí),CPU從SS和SP中得到棧頂?shù)牡刂贰?/p>

現(xiàn)在,我們可以完整地描述push和pop指令的功能了,例如push ax。

push ax的執(zhí)行,由以下兩步完成。

SP=SP-2, SS:SP指向當(dāng)前棧頂前面的單元,以當(dāng)前棧頂前面的單元為新的棧頂;

將ax中的內(nèi)容送入SS:SP指向的內(nèi)存單元處,SS:SP 此時(shí)指向新棧頂。

圖3.10描述了8086CPU對(duì)push指令的執(zhí)行過(guò)程。

push指令的執(zhí)行過(guò)程

從圖中我們可以看出,8086CPU中,入棧時(shí),棧頂從高地址向低地址方向增長(zhǎng)。

如果將10000H~ 1000FH這段空間當(dāng)作棧,初始狀態(tài)棧是空的,此時(shí),SS=1000H,SP=?

將10000H~1000H這段空間當(dāng)作棧段,SS=100H,??臻g大小為16字節(jié),棧最底部的字單元地址為1000:000任意時(shí)刻,SS:SP 指向棧項(xiàng),當(dāng)棧中只有-個(gè)元素的時(shí)候,SS=1000H, SP=000EH。 棧為空,就相當(dāng)于棧中唯 一的元素出棧, 出棧后,SP=SP+2,SP原來(lái)為000EH, 加2后SP=10H, 所以,當(dāng)棧為空的時(shí)候,SS=1000H,SP=10H。

換一個(gè)角度看,任意時(shí)刻,SS:SP指向棧項(xiàng)元素,當(dāng)棧為空的時(shí)候,棧中沒(méi)有元素,也就不存在棧項(xiàng)元素,所以SS:SP只能指向棧的最底部單元下面的單元,該單元的偏移地址為棧最底部的字單元的偏移地址+2,棧最底部字單元的地址為1000:000E,所以??諘r(shí),SP=0010H。

我們描述pop指令的功能,例如pop ax。

popax的執(zhí)行過(guò)程和push ax剛好相反,由以下兩步完成。

將SS:SP指向的內(nèi)存單元處的數(shù)據(jù)送入ax中;

注意,圖3.2中,出棧后,SS:SP指向新的棧頂1000EH,pop操作前的棧頂元素,1000CH處的2266H依然存在,但是,它已不在棧中。當(dāng)再次執(zhí)行push等入棧指令后,SS:SP移至1000CH并在里面寫(xiě)入新的數(shù)據(jù),它將被覆蓋。

棧頂超界的問(wèn)題

8086CPU用SS和SP指示棧頂?shù)牡刂?,并提供push和pop指令實(shí)現(xiàn)入棧和出棧。

SS和SP只是記錄了棧頂?shù)牡刂?,依靠SS和SP可以保證在入棧和出棧時(shí)找到棧頂。可是,如何能夠保證在入棧、出棧時(shí),棧頂不會(huì)超出??臻g?

圖3.13描述了在執(zhí)行push指令后,棧頂超出棧空間的情況。

圖3.13中,將10010H~1001FH當(dāng)作??臻g,該??臻g容量為16字節(jié)(8字),初始狀態(tài)為空,SS=1000H、 SP=0020H,SS:SP指向10020H;

在執(zhí)行8次push ax后,向棧中壓入8個(gè)字,棧滿,SS:SP 指向10010H;

再次執(zhí)行push?ax:?sp=sp-2,?SS:SP指向1000EH,棧頂超出了棧空間,ax?中的數(shù)據(jù)送入1000EH單元處,將棧空間外的數(shù)據(jù)覆蓋。

圖3.14描述了在執(zhí)行pop指令后,棧頂超出??臻g的情況。

圖3.14中,將10010H~1001FH?當(dāng)作??臻g,該棧空間容量為16字節(jié)(8字),當(dāng)前狀態(tài)為滿,SS=1000H、SP=0010H,SS:SP?指向10010H;

在執(zhí)行8次popax后,從棧中彈出8個(gè)字,???,SS=SP指向10020H;

再次執(zhí)行pop ax: sp sp+2, SS:SP指向1022H棧頂超出了??臻g。此后,如果再執(zhí)行push指令,10020H、 10021H 中的數(shù)據(jù)將被覆蓋。

上面描述了執(zhí)行push、 pop指令時(shí),發(fā)生的棧頂超界問(wèn)題。可以看到,當(dāng)棧滿的時(shí)候再使用push指令入棧,或??盏臅r(shí)候再使用pop指令出棧,都將發(fā)生棧頂超界問(wèn)題。

棧頂超界是危險(xiǎn)的,因?yàn)槲覀兗热粚⒁欢慰臻g安排為棧, 那么在??臻g之外的空間里很可能存放了具有其他用途的數(shù)據(jù)、代碼等,這些數(shù)據(jù)、代碼可能是我們自己程序中的,也可能是別的程序中的(畢競(jìng)一個(gè)計(jì)算機(jī)系統(tǒng)中并不是只有我們自己的程序在運(yùn)行)。但是由于我們?cè)谌霔3鰲r(shí)的不小心,而將這些數(shù)據(jù)、代碼意外地改寫(xiě),將會(huì)引發(fā)連串的錯(cuò)誤。

我們當(dāng)然希望CPU可以幫我們解決這個(gè)問(wèn)題,比如說(shuō)在CPU中有記錄棧頂上限和棧底的寄存器,我們可以通過(guò)填寫(xiě)這些寄存器米指定棧空間的范圍,然后,CPU在執(zhí)行push指令的時(shí)候靠檢測(cè)棧頂上限寄存器、在執(zhí)行pop指令的時(shí)候靠檢測(cè)棧底寄存器保證不會(huì)超界。

不過(guò),對(duì)于8086CPU,這只是我們的一個(gè)設(shè)想(我們當(dāng)然可以這樣設(shè)想,如果CPU是我們?cè)O(shè)計(jì)的話,這也就不僅僅是一個(gè)設(shè)想)。實(shí)際的情況是,8086CPU 中并沒(méi)有這樣的寄存器。

8086CPU不保證我們對(duì)棧的操作不會(huì)超界。這也就是說(shuō),8086CPU只知道棧頂在何處(由SS:SP指示),而不知道我們安排的??臻g有多大。這點(diǎn)就好像CPU只知道當(dāng)前要執(zhí)行的指令在何處(由CS:IP 指示),而不知道要執(zhí)行的指令有多少。從這兩點(diǎn)上我們可以看出8086CPU的工作機(jī)理,它只考慮當(dāng)前的情況:當(dāng)前的棧頂在何處、當(dāng)前要執(zhí)行的指令是哪一條。

我們?cè)诰幊痰臅r(shí)候要自己操心棧頂超界的問(wèn)題,要根據(jù)可能用到的最大??臻g,來(lái)安排棧的大小,防止入棧的數(shù)據(jù)太多而導(dǎo)致的超界;執(zhí)行出棧操作的時(shí)候也要注意,以防??盏臅r(shí)候繼續(xù)出棧而導(dǎo)致的超界。

push、pop指令

前面我們一直在使用push ax和pop ax,顯然push和pop指令是可以在寄存器和內(nèi)存(??臻g當(dāng)然也是內(nèi)存空間的一部分, 它只是段可以以一種特殊的方式進(jìn)行訪問(wèn)的內(nèi)存空間。)之間傳送數(shù)據(jù)的。

push和pop指令的格式可以是如下形式:

push寄存器? ;將一個(gè)寄存器中的數(shù)據(jù)入棧

pop寄存器? ;出棧,用一個(gè)寄存器接收出棧的數(shù)據(jù)

當(dāng)然也可以是如下形式:

push段寄存器? ;將一個(gè)段寄存器中的數(shù)據(jù)入棧

pop段寄存器? ;出棧,用一個(gè)段寄存器接收出棧的數(shù)據(jù)

push和pop也可以在內(nèi)存單元和內(nèi)存單元之間傳送數(shù)據(jù),我們可以:

push內(nèi)存單元? ;將一個(gè)內(nèi)存字單元處的字入棧(注意:棧操作都是以字為單位)

pop內(nèi)存單元? ;出棧,用一個(gè)內(nèi)存字單元接收出棧的數(shù)據(jù)

mov ax, 1000H

mov ds,ax? ;內(nèi)存單元的段地址要放在ds中

push [0]? ;將1000:0處的字壓入棧中

pop [2]? ;出棧,出棧的數(shù)據(jù)送入1000:2處

指令執(zhí)行時(shí),CPU要知道內(nèi)存單元的地址,可以在push、pop指令中只給出內(nèi)存單元的偏移地址,段地址在指令執(zhí)行時(shí),CPU 從ds中取得。

編程,將10000H~1000FH這段空間當(dāng)作棧,初始狀態(tài)棧是空的,將AX、BX、DS中的數(shù)據(jù)入棧。

mov ax, 1000H

mov ss, ax? ;設(shè)置棧的段地址,Ss=1000H, 不能直接向段寄存器 SS中送入;數(shù)據(jù), 所以用ax中轉(zhuǎn)。

mov sp, 0010H? ;設(shè)置棧頂?shù)钠频刂?,因棧為空,所以sp=0010H。上面的3條指令設(shè)置棧頂?shù)刂贰>幊讨幸约鹤⒁鈼5拇笮 ?/p>

push ax

push bx

push ds

將10000H~1000FH這段空間當(dāng)作棧,初始狀態(tài)棧是空的;

設(shè)置AX=001AH,BX=001BH;

將AX、BX中的數(shù)據(jù)入棧;

然后將AX、BX清零;

從棧中恢復(fù)AX、BX原來(lái)的內(nèi)容。

mov ax, 1000Hmov ss, ax

mov sp, 0010H? ;初始化棧頂,棧的情況如圖3.15(a)所示

mov ax, 001AH

mov bx, 001BH

push ax

push bx? ;ax、bx入棧,棧的情況如圖3.15(b)所示

sub ax, ax? ?;將ax清零,也可以用mov ax,0,

;sub ax,ax 的機(jī)器碼為2個(gè)字節(jié),

;mov ax,0 的機(jī)器碼為3個(gè)字節(jié)。

sub bx,bx

pop bx? ;從棧中恢復(fù) ax、bx原來(lái)的數(shù)據(jù), 當(dāng)前棧頂?shù)膬?nèi)容是bx

pop ax;中原來(lái)的內(nèi)容: 001BH,ax 中原來(lái)的內(nèi)容001AH在棧頂

;的下面,所以要先pop bx,然后再pop ax。

從上面的程序我們看到,用棧來(lái)暫存以后需要恢復(fù)的寄存器中的內(nèi)容時(shí),出棧的順序要和入棧的順序相反,因?yàn)樽詈笕霔5募拇嫫鞯膬?nèi)容在棧頂,所以在恢復(fù)時(shí),要最先出棧。

將10000H~1000FH這段空間當(dāng)作棧,初始狀態(tài)棧是空的;

設(shè)置AX=001AH,BX=001BH;

利用棧,交換AX和BX中的數(shù)據(jù)。

mov ax, 1000H

mov ss, ax

mov sp, 0010H? ;初始化棧頂,棧的情況如圖3.16(a)所示

mov ax, 001AH

mov bx, 001BH

push ax

push bx? ;ax、bx入棧,棧的情況如圖3.16(b)所示

pop ax? ;當(dāng)前棧項(xiàng)的數(shù)據(jù)是bx中原來(lái)的數(shù)據(jù): 001BH;

;所以先pop ax, ax=001BH;

pop bx? ?;執(zhí)行pop ax后,棧頂?shù)臄?shù)據(jù)為ax原來(lái)的數(shù)據(jù):

;所以再pop bx, bx=001AH;

push、 pop實(shí)質(zhì)上就是一種內(nèi)存?zhèn)魉椭噶?,可以在寄存器和?nèi)存之間傳送數(shù)據(jù),與mov指令不同的是,push和pop指令訪問(wèn)的內(nèi)存單元的地址不是在指令中給出的,而是由SS:SP指出的。同時(shí),push和pop指令還要改變SP中的內(nèi)容。

我們要十分清楚的是,push 和pop指令同mov指令不同,CPU執(zhí)行mov指令只需一步操作,就是傳送,而執(zhí)行push、 pop指令卻需要兩步操作。執(zhí)行push 時(shí),CPU的兩步操作是:先改變SP,后向SS:SP 處傳送。執(zhí)行pop時(shí),CPU的兩步操作是:先讀取SS:SP處的數(shù)據(jù),后改變SP。

注意,push, pop 等棧操作指令,修改的只是SP。也就是說(shuō),棧頂?shù)淖兓秶畲鬄? 0~FFFFH。

提供:SS、SP指示棧頂:改變SP后寫(xiě)內(nèi)存的入棧指令;讀內(nèi)存后改變SP的出棧指令。這就是8086CPU提供的棧操作機(jī)制。

棧的綜述

8086CPU提供了棧操作機(jī)制,方案如下。

在SS、SP中存放棧頂?shù)亩蔚刂泛推频刂?提供入棧和出棧指令,它們根據(jù)SS:SP指示的地址,按照棧的方式訪問(wèn)內(nèi)存單元。

push指令的執(zhí)行步驟:SP=SP-2;向SS:SP指向的字單元中送入數(shù)據(jù)。

pop 指令的執(zhí)行步驟:從SS:SP指向的字單元中讀取數(shù)據(jù):SP=SP+2。

任意時(shí)刻,SS:SP 指向棧頂元素。

8086CPU只記錄棧項(xiàng),??臻g的大小我們要自己管理。

用棧來(lái)暫存以后需要恢復(fù)的寄存器的內(nèi)容時(shí),寄存器出棧的順序要和入棧的順序相反。

push、pop 實(shí)質(zhì)上是一種內(nèi) 存?zhèn)魉椭噶?,注意它們的靈活應(yīng)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第3章、寄存器(內(nèi)存訪問(wèn)) 3.1 內(nèi)存中字的存儲(chǔ) 字單元:存放一個(gè)字型數(shù)據(jù)(16位)的內(nèi)存單元,由兩個(gè)連續(xù)的內(nèi)存...
    BoL0150閱讀 1,134評(píng)論 0 0
  • CPU中,用16位寄存器來(lái)存儲(chǔ)一個(gè)字。高8位存放高位字節(jié),低8位存放低位字節(jié)。在內(nèi)存中存儲(chǔ)時(shí),由于內(nèi)存單元是字節(jié)單...
    guanjianhe閱讀 176評(píng)論 0 0
  • 1. CPU中用16位寄存器來(lái)存儲(chǔ)一個(gè)字,高8位存放高位字節(jié),低8位存放低位字節(jié)。在內(nèi)存中存儲(chǔ)時(shí),由于內(nèi)存單元是字...
    八斗道人閱讀 570評(píng)論 0 1
  • 棧結(jié)構(gòu):棧是一種只能在一端插入或刪除操作的數(shù)據(jù)結(jié)構(gòu) 棧有兩個(gè)基本操作: 入棧和出棧 入棧: 將一個(gè)新的元素放到棧...
    有機(jī)會(huì)一起坐牢閱讀 1,322評(píng)論 0 1
  • 8086的尋址方式 CPU訪問(wèn)內(nèi)存單元時(shí),要給出內(nèi)存單元的地址,所有的內(nèi)存單元都有唯一的地址,叫做物理地址 808...
    hfzhangzhang閱讀 1,271評(píng)論 0 0

友情鏈接更多精彩內(nèi)容