今天突然想分析一下函數在相互調用過程中棧幀的變化,還是想盡量以比較清晰的思路把這一過程描述出來,關於c函數調用原理的理解是很重要的。
1.關於棧
首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從內存高地址->地地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中
---> 寄存器ebp(base pointer )可稱為“幀指針”或“基址指針”,其實語意是相同的。
---> 寄存器esp(stack pointer)可稱為“ 棧指針”。
要知道的是:
---> ebp 在未受改變之前始終指向棧幀的開始,也就是棧底,所以ebp的用途是在堆棧中尋址用的。
---> esp是會隨著數據的入棧和出棧移動的,也就是說,esp始終指向棧頂。
見下圖,假設函數A調用函數B,我們稱A函數為"調用者",B函數為“被調用者”則函數調用過程可以這麼描述:
(1)先將調用者(A)的堆棧的基址(ebp)入棧,以保存之前任務的信息。
(2)然後將調用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調用者B的棧底)。
(3)然後在這個基址(被調用者B的棧底)上開辟(一般用sub指令)相應的空間用作被調用者B的棧空間。
(4)函數B返回後,從當前棧幀的ebp即恢復為調用者A的棧頂(esp),使棧頂恢復函數B被調用前的位置;然後調用者A再從恢復後的棧頂可彈出之前的ebp值(可以這麼做是因為這個值在函數調用前一步被壓入堆棧)。這樣,ebp和esp就都恢復了調用函數B前的位置,也就是棧恢復函數B調用前的狀態。
這個過程在AT&T匯編中通過兩條指令完成,即:
leave
ret
這兩條指令更直白點就相當於:
mov %ebp , %esp
pop %ebp