基本概念
棧(stack)又名堆疊,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。這一端被稱為棧頂,相對地,把另一端稱為棧底。不含任何元素的棧稱為空棧。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。
如圖1所示:假設一個棧S中的元素為 ,則稱a1為棧底元素,an為棧頂元素。
圖1
棧地址是指棧頂的地址。在Windows下,棧是向低地址擴展的數據結構,是一塊連續的記憶體的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
棧與棧地址的操作
棧是由系統自動分配和回收的記憶體。如一個子函式被調用時,系統會將函式內的局部變數的記憶體單元分配到棧上,當函式執行完畢時系統自動釋放所分配的棧地址單元。棧的修改是按後進先出的原則進行的。棧又稱為後進先出(Last In First Out)表,簡稱為LIFO表。
在計算機系統中,棧則是一個具有以上屬性的動態記憶體區域。程式可以將數據壓入棧中,也可以將數據從棧頂彈出。在i386機器中,棧頂由稱為esp的暫存器進行定位。壓棧的操作使得棧頂的地址增大,彈出的操作使得棧頂的地址減小。
棧和棧地址的特點
以下是棧和棧地址在程式運行中的特點:
(1)棧經常與 sp 暫存器一起工作,最初 sp 指向棧頂(棧的高地址),即棧地址。
(2)CPU 用 push 指令來將數據壓棧,用 pop 指令來彈棧。當用 push 壓棧時,sp 值減少(向低地址擴展)。當用 pop 彈棧時,sp 值增大。存儲和獲取數據都是 CPU 暫存器的值。
(3)當函式被調用時,CPU使用特定的指令把當前的 IP 壓棧。即執行代碼的地址。CPU 接下來將調用函式地址賦給 IP ,進行調用。當函式返回時,舊的 IP 被彈棧,CPU 繼續去函式調用之前的代碼。
(4)當進入函式時,sp 向下擴展,擴展到確保為函式的局部變數留足夠大小的空間。如果函式中有一個 32-bit 的局部變數會在棧中留夠四位元組的空間。當函式返回時,sp 通過返回原來的位置來釋放空間。
(5)如果函式有參數的話,在函式調用之前,會將參數壓棧。函式中的代碼通過 sp 的當前位置來定位參數並訪問它們。
(6)函式嵌套調用和使用魔法一樣,每一次新調用的函式都會分配函式參數,返回值地址、局部變數空間、嵌套調用的活動記錄都要被壓入棧中。函式返回時,按照正確方式的撤銷。
(7)棧要受到記憶體塊的限制,不斷的函式嵌套/為局部變數分配太多的空間,可能會導致棧溢出。當棧中的記憶體區域都已經被使用完之後繼續向下寫(低地址),會觸發一個 CPU 異常。這個異常接下會通過語言的運行時轉成各種類型的棧溢出異常。
棧地址的分配
棧的分配和釋放可以這樣子理解:棧記憶體塊在計算機中不可能會移動,它的地址已經被固定。系統分不分配它,它就在那裡。當為局部變數分配棧記憶體時,系統就將局部變數存入到棧的某個記憶體塊中;當子函式運行結束局部變數應當被釋放時,系統再將這些存入局部變數的棧記憶體中的數據清除掉,恢復原來沒有被初始化的狀態。
在函式調用時,在大多數的C編譯器中,參數是由右往左入棧的,然後是函式中的局部變數。注意靜態變數是不入棧的。當本次函式調用結束後,局部變數先出棧,然後是參數,最後棧頂指針指向函式的返回地址,也就是主函式中的下一條指令的地址,程式由該點繼續運行。
棧、堆、靜態存儲區和常量存儲區
在C++中,記憶體分成4個區,他們分別是堆、棧、靜態存儲區和常量存儲區。下面是四個區的特點與區別:
(1)棧:就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的存儲區。裡面的變數通常是局部變數、函式參數等。
(2)堆:又叫自由存儲區,它是在程式執行的過程中動態分配的,它最大的特性就是動態性。堆就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new就要對應一個delete。如果程式設計師沒有釋放掉,那么在程式結束後,作業系統會自動回收。
(3)靜態存儲區:所有的靜態對象,全局對象都於靜態存儲區分配。
(4)常量存儲區:這是一塊比較特殊的存儲區,他們裡面存放的是常量,不允許修改。常量字元串都存放在靜態存儲區,返回的是常量字元串的首地址。
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。