首先,你要明白一個概念,指針,是做什么的?答案是,指針,是指向地址的。程序指針,指向的空間,在物理上是Flash,在邏輯上,就是代碼空間。比如說51單片機的PC指針,指向的就是Flash,即程序下一步要執(zhí)行的指令的地址。
數(shù)據(jù)指針,指向的空間,在物理上有Flash和RAM,在邏輯上是Flash里的常數(shù)空間和數(shù)據(jù)空間,注意,是對于單片機來說,對于我們的電腦,常數(shù)空間不是在Flash上。
比如說51單片機的DPTR,如果用MOVC A,@A+DPTR,此時,就是指向常數(shù)空間,如果用MOVX A,@A+DPTR就是指向的數(shù)據(jù)空間。
堆棧指針,指向的空間,在物理上是RAM,在邏輯上,就是數(shù)據(jù)空間,是特定的數(shù)據(jù)空間,堆棧是數(shù)據(jù)空間中單獨劃分出來,專門用于寄存中間結果的內(nèi)存空間。
數(shù)據(jù)指針和堆棧指針主要有兩個區(qū)別:
一是數(shù)據(jù)指針可以指向Flash,即可以指向常數(shù),比如說我們定義一個數(shù)組 unsigned char code Table[99],此時,就是DPTR可以指向常數(shù)空間。堆棧指針是不可以的,只能是指向RAM。
第二個區(qū)別,堆棧指針指向的是特定的數(shù)據(jù)空間,這個特定的數(shù)據(jù)空間,是從整個數(shù)據(jù)空間里劃分出來,專門用于作堆棧用的,堆棧區(qū)間一旦劃分出來,堆棧指針在規(guī)則上,就只能在這個范圍內(nèi)活動,如果出了這個范圍,可能導致整個程序的崩潰。而數(shù)據(jù)指針在規(guī)則上,可以指向整個數(shù)據(jù)空間,但是,可以讀堆??臻g,不應該去修改,否則也可能導致程序的崩潰。
在51單片機中,SP棧指針是一個專用的8位寄存器,
系統(tǒng)復位后,SP初始化為07H,使得堆棧指針實際上是由08H單元開始。 在響應中斷或子程序調(diào)用時,發(fā)生入棧操作,入棧的是16位PC值; 51中有PUSH壓入和POP彈出棧操作指令,
如有必要,在中斷或調(diào)用子程序時可用POSU指令把PSW或其它需要保護的寄存器的內(nèi)容
壓入堆棧加以保護;返回前再使用POP指令把它們恢復。
51的內(nèi)部RAM只有從00H到7FH共計128字節(jié)的空間,而且00H~1FH是工作寄存器區(qū), 所以SP的設定一般設定是從20H到70H這個范圍。 51堆棧的容量最大也不會超過128字節(jié)。
1、在計算機領域,堆棧是一個不容忽視的概念,但是很多人甚至是計算機專業(yè)的人也沒有明確堆棧其實是兩種數(shù)據(jù)結構。堆棧都是一種數(shù)據(jù)項按序排列的數(shù)據(jù)結構,只能在一端(稱為棧頂(top))對數(shù)據(jù)項進行插入和刪除。要點:堆,順序隨意。棧,后進先出(Last-In/First-Out)。區(qū)分隊列 先進先出
2、堆棧是一塊保存數(shù)據(jù)的連續(xù)內(nèi)存。 一個名為堆棧指針(SP)的寄存器指向堆棧的頂部。 堆棧的底部在一個固定的地址。 堆棧的大小在運行時由內(nèi)核動態(tài)地調(diào)整。 CPU實現(xiàn)指令 PUSH和POP, 向堆棧中添加元素和從中移去元素。 堆棧由邏輯堆棧幀組成。
當調(diào)用函數(shù)時邏輯堆棧幀被壓入棧中, 當函數(shù)返回時邏輯 堆棧幀被從棧中彈出。 堆棧幀包括函數(shù)的參數(shù), 函數(shù)地局部變量, 以及恢復前一個堆棧 幀所需要的數(shù)據(jù), 其中包括在函數(shù)調(diào)用時指令指針(IP)的值。 堆棧既可以向下增長(向內(nèi)存低地址)也可以向上增長, 這依賴于具體的實現(xiàn)。 在我 們的例子中, 堆棧是向下增長的。 這是很多計算機的實現(xiàn)方式, 包括Intel, Motorola, SPARC和MIPS處理器。
堆棧指針(SP)也是依賴于具體實現(xiàn)的。 它可以指向堆棧的最后地址, 或者指向堆棧之后的下一個空閑可用地址。 在我們的討論當中, SP指向堆棧的最后地址。 除了堆棧指針(SP指向堆棧頂部的的低地址)之外, 為了使用方便還有指向幀內(nèi)固定 地址的指針叫做幀指針(FP)。 有些文章把它叫做局部基指針(LB-local base pointer)。 從理論上來說, 局部變量可以用SP加偏移量來引用。 然而, 當有字被壓棧和出棧后, 這 些偏移量就變了。
盡管在某些情況下編譯器能夠跟蹤棧中的字操作, 由此可以修正偏移 量, 但是在某些情況下不能。 而且在所有情況下, 要引入可觀的管理開銷。 而且在有些 機器上, 比如Intel處理器, 由SP加偏移量訪問一個變量需要多條指
令才能實現(xiàn)。 因此, 許多編譯器使用第二個寄存器, FP, 對于局部變量和函數(shù)參數(shù)都可以引用, 因為它們到FP的距離不會受到PUSH和POP操作的影響。 在Intel CPU中, BP(EBP)用于這 個目的。 在Motorola CPU中, 除了A7(堆棧指針SP)之外的任何地址寄存器都可以做FP。 考慮到我們堆棧的增長方向, 從FP的位置開始計算, 函數(shù)參數(shù)的偏移量是正值, 而局部 變量的偏移量是負值。 當一個例程被調(diào)用時所必須做的第一件事是保存前一個FP(這樣當例程退出時就可以 恢復)。 然后它把SP復制到FP, 創(chuàng)建新的FP, 把SP向前移動為局部變量保留空間。 這稱為 例程的序幕(prolog)工作。 當例程退出時, 堆棧必須被清除干凈, 這稱為例程的收尾 (epilog)工作。 Intel的ENTER和LEAVE指令, Motorola的LINK和UNLINK指令, 都可以用于 有效地序幕和收尾工作。
3、普通的8051MCU堆棧指針只有8位,所以堆棧不可能超過256字節(jié)13086.
SP:堆棧指針(SP,Stack Pointer),專門用于指出堆棧頂部數(shù)據(jù)的地址。
堆棧介紹:日常生活中,我們都注意到過這樣的現(xiàn)象,家里洗的碗,一只一只摞起來,最晚放上去的放在最上面,而最早放上去的則放在最下面,在取的時候正好相反,先從最上面取,這種現(xiàn)象我們用一句話來概括:“先進后出,后進先出”。請大家想想,還有什么地方有這種現(xiàn)象?其實比比皆是,建筑工地上堆放的磚頭、材料,倉庫里放的貨物,都是“先進后出,后進先出”,這實際是一種存取物品的規(guī)則,我們稱之為“堆?!薄?/p>
在單片機中,我們也能在RAM中構造這樣一個區(qū)域,用來存放數(shù)據(jù),這個區(qū)域存放數(shù)據(jù)的規(guī)則就是“先進后出,后進先出”,我們稱之為“堆棧”。為什么需要這樣來存放數(shù)據(jù)呢?存儲器本身不是能按地址來存放數(shù)據(jù)嗎?對,知道了地址的確就能知道里面的內(nèi)容,但如果我們需要存放的是一批數(shù)據(jù),每一個數(shù)據(jù)都需要知道地址那不是麻煩嗎?如果我們讓數(shù)據(jù)一個接一個地放置,那么我們只要知道第一個數(shù)據(jù)所在地址單元就能了(看圖2)如果第一個數(shù)據(jù)在27H,那么第二、三個就在28H、29H了。所以利用堆棧這種辦法來放數(shù)據(jù)能簡化操作
那么51中堆棧什么地方呢?單片機中能存放數(shù)據(jù)的區(qū)域有限,我們不能夠?qū)iT分配一塊地方做堆棧,所以就在內(nèi)存(RAM)中開辟一塊地方,用于堆棧,但是用內(nèi)存的哪一塊呢?還是不好定,因為51是一種通用的單片機,各人的實際需求各不相同,有人需要多一些堆棧,而有人則不需要那么多,所以怎么分配都不合適,怎樣來解決這個問題?分不好干脆就不分了,把分的權利給用戶(編程者),根據(jù)自已的需要去定吧,所以51單片機中堆棧的位置是能變化的。而這種變化就體現(xiàn)在SP中值的變化,看圖2,SP中的值等于27H不就相當于是一個指針指向27H單元嗎?當然在真正的51機中,開始指針所指的位置并非就是數(shù)據(jù)存放的位置,而是數(shù)據(jù)存放的前一個位置,比如一開始指針是指向27H單元的,那么第一個數(shù)據(jù)的位置是28H單元,而不是27H單元,為什么會這樣?
什么是堆棧?MCS-51單片機的堆棧怎樣設置的?
答:程序設計時,往往需要一個后進先出的RAM區(qū),以保存CPU的現(xiàn)場。這種后進先出的緩沖區(qū),就稱為堆棧。
MCS-51單片的堆棧原則上設在內(nèi)部RAM的任意區(qū)域內(nèi) 。但是,一般設在31H~7FH的范圍之間,棧頂?shù)奈恢糜蓷V羔楽P指出。
51單片機堆棧操作指令舉例說明
這4類指令的作用是把直接尋址單元的內(nèi)容傳送到堆棧指針SP所指的單元中,以及把SP
所指單元的內(nèi)容送到直接尋址單元中。這類指令只有兩條,下述的第一條常稱為入棧操作指令,第二條稱為出棧操作指令。需要指出的是,單片機開機復位后,(SP)默認為07H,但一般都需要重新賦值,設置新的SP首址。入棧的第一個數(shù)據(jù)必須存放于SP+1所指存儲單元,故實際的堆棧底為SP+1所指的存儲單元。
堆棧操作指令有兩條: PUSH direct POP direct 第一條指令稱之為推入,就是將direct中的內(nèi)容送入堆棧中,第二條指令稱之為彈出,就是將堆棧中的內(nèi)容送回到direct中。推入指令的執(zhí)行過程是,首先將SP中的值加1,然后把SP中的值當作地址,將direct中的值送進以 堆棧操作指令有兩條: PUSH direct POP direct
第一條指令稱之為推入,就是將direct中的內(nèi)容送入堆棧中,第二條指令稱之為彈出,就是將堆棧中的內(nèi)容送回到direct中。推入指令的執(zhí)行過程是,首先將SP中的值加1,然后把SP中的值當作地址,將direct中的值送進以SP中的值為地址的RAM單元中。例: MOV SP,
#5FH MOV A,
#100 MOV B,
#20 PUSH ACC
PUSH B
則執(zhí)行第一條PUSH ACC指令是這樣的:將SP中的值加1,即變?yōu)?0H,然后將A中的值送到60H單元中,因此執(zhí)行完本條指令后, 內(nèi)存60H單元的值就是100,同樣,執(zhí)行PUSH B時,是將SP+1,即變?yōu)?1H,然后將B中的值送入到61H單元中,即執(zhí)行完本條指令后,61H單元中的值變?yōu)?0。
POP指令的在單片機中執(zhí)行是這樣的,首先將SP中的值作為地址,并將此地址中的數(shù)送到POP指令后面的那個direct中,然后SP減1。 接上例: POP B POP ACC 則執(zhí)行過程是:將SP中的值(現(xiàn)在是61H)作為地址,取61H單元中的數(shù)值(現(xiàn)在是20),送到B中,所以執(zhí)行完本條指令后B中的值是20,然后將SP減1,因此本條指令執(zhí)行完后,SP的值變?yōu)?0H,然后執(zhí)行POP ACC,將SP中的值(60H)作為地址,從該地址中取數(shù)(現(xiàn)在是100),并送到ACC中,所以執(zhí)行完本條指令后,ACC中的值是100。
這有什么意義呢?ACC中的值本來就是100,B中的值本來就是20,是的,在本例中,的確沒有意義,但在實際工作中,則在PUSH B后一般要執(zhí)行其他指令,而且這些指令會把A中的值,B中的值改掉,所以在程序的結束,如果我們要把A和B中的值恢復原值,那么這些指令就有意義了。 還有一個問題,如果我不用堆棧,比如說在PUSH ACC指令處用MOV 60H,A,在PUSH B處用指令MOV 61H,B,然后用MOV A,60H,MOV B,61H來替代兩條POP指令,不是也一樣嗎?是的,從結果上看是一樣的,但是從過程看是不一樣的,PUSH和POP指令
都是單字節(jié),單周期指令,而MOV指令則是雙字節(jié),雙周期指令。更何況,堆棧的作用不止于此,所以一般的計算機上都設有堆棧,單片機也是一樣,而我們在編寫子程序,需要保存數(shù)據(jù)時,常常也不采用后面的辦法,而是用堆棧的辦法來實現(xiàn)。 例:寫出以下單片機程序的運行結果 MOV 30H,
#12 MOV 31H,
#23 PUSH 30H
PUSH 31H POP 30H POP 31H
結果是30H中的值變?yōu)?3,而31H中的值則變?yōu)?2。也就兩者進行了數(shù)據(jù)交換。從這個例程能看出:使用堆棧時,入棧的書寫次序和出棧的書寫次序必須相反,才能保證數(shù)據(jù)被送回原位,不然就要出錯了。 另外特別注意事項:
進行堆棧操作時,我們不能: PUSH R0 PUSH R1 而只能: PUSH 00H PUSH 01H POP也是一樣。
評論
查看更多