在談棧幀之前,我們必須要先知道c/c++程式記憶體的分配情況。
乙個由c/c++編譯的程式占用的記憶體分為以下幾個部分:
1、棧區(stack)— 由編譯器自動分配釋放 ,存放為執行函式而分配的局
部變數、函式引數、返回資料、返回位址等。其操作方式類似於資料結構中的
棧。 2、堆區(heap) — 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束
時可能由os** 。分配方式類似於鍊錶。
3、全域性區(靜態區)(static)—存放全域性變數、靜態資料、常量。程式結
束后由系統釋放。
4、文字常量區 —常量字串就是放在這裡的。 程式結束後由系統釋放。
5、程式**區—存放函式體(類成員函式和全域性函式)的二進位制**。
記憶體分配的方式有三種:
1.從靜態儲存區域分配:內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在。例如全域性變數,static變數。
2.在棧上建立:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
3.從堆上分配:
亦稱動態記憶體分配。
程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由程式設計師決定,使用非常靈活,但如果在堆上分配了空間,就有責任**它,否則執行的程式會出現記憶體洩漏,頻繁地分配和釋放不同大小的堆空間將會產生堆記憶體碎塊。
如圖所示:
從圖中可以看出,棧區與堆區相對而生。函式的呼叫過程就是在棧上發生的,今天就深入的研究一下函式的呼叫過程。
先看一段簡單的**:
#include
int add(int
x, int
y)int main()
當進行程式除錯的時候,檢視【呼叫堆疊】,如下圖:
我們可以發現其實main函式在__tmaincrtstartup函式中呼叫的,而__tmaincrtstartup函式是在maincrtstartup中被呼叫。換而言之,main函式雖然是程式的入口,可是並不是程式執行時第乙個被呼叫的函式,第乙個被呼叫的函式是maincrtstartup。
我們知道每一次函式呼叫都是乙個過程。
這個過程我們通長稱之為:函式的呼叫過程。
這個過程要為函式開闢棧空間,用於本次函式的呼叫中臨時變數的儲存、現場保護。這塊棧空間我們稱之為函式棧幀。
而棧幀的維護我們必須了解ebp和esp兩個暫存器。在函式呼叫的過程中這兩個暫存器存放了維護這個棧的棧底和棧頂指標。
比如:
呼叫main函式,我們為main函式分配棧幀空間,那麼棧幀維護如下:
ebp存放了指向函式棧幀棧底的位址。
esp 存放了指向函式棧幀棧頂的位址。
當我們要詳細研究函式呼叫過程,必須得對應彙編**。
1. 從main函式的地方開始,要展開main函式的呼叫就得為main函式建立棧幀,那我們先來看main函式棧幀的建立。
2.接下來是add函式的呼叫。
引數傳遞過程:
執行call指令的時候按f11,來到了這裡。
再按f11就進入add函式的執行**處。
剩下的是函式返回部分:
每次呼叫一次函式都會形成乙個棧幀結構,在該函式返回時釋放棧幀結構。
注:棧幀這部分內容在不同的編譯器上實現存在差異,但是思想都是一致的。
函式呼叫過程(棧幀)
眾所周知,程式每呼叫乙個函式,系統都會為其開闢一塊空間,當它返回時,才收回這塊空間。程式崩潰有一部分原因就是因為無限制的呼叫函式,卻沒有及時返回,導致記憶體空間不夠。為了更好的維護這一塊空間 通常稱為棧空間 我們需要了解兩個暫存器,乙個為 esp 指向棧頂的指標 乙個為 ebp 指向棧底的指標 棧空...
函式棧幀(呼叫過程)
函式棧幀就是在呼叫函式是為其在棧空間上開闢了一段空間,指向過程呼叫,乙個過程呼叫包括將資料 以過程引數和返回值的形式 和控制從 的一部分傳遞到另一部分。我們以以下 為例講解整個函式呼叫過程 int my add int x,int y int main 一 呼叫main 函式 我們從main 函式的...
棧幀(函式的呼叫過程)
函式 乙個函式的呼叫包括將資料 以引數和返回值的形式 和控制從 的一部分傳遞到另一部分。另外,他還必須在進入時為函式的區域性變數分配空間。並在退出時釋放這些空間。而其中資料傳遞 區域性變數的分配和釋放都是通過操縱棧來實現的。棧幀 棧用來傳遞函式引數 儲存返回資訊 儲存暫存器用以以後的恢復 以及本地儲...