函式在實現過程記憶體中的壓棧和出棧
關於函式在呼叫過程中的壓棧和出棧問題在學習的時候就感覺很經典,對程式的把握可以提升一個臺階。
一.首先讓我們寫出一個簡單的函式。(我是在vc6.0中實現,並不表示vs編譯器底下不可以實現)。
#include<stdio.h>
int add(num1,num2)
{
int ret = 0;
ret = num1+num2;
return ret;
}
int main()
{
int num1 = 1;
int num2 = 2;
int ret = add(num1,num2);
printf("%d ",ret);
return 0;
}
1).需要宣告是add函式中可以直接寫成"return num1+num2",我在寫部落格的時候是故意寫成這樣,以便於後面的分析。
二.接下來,我們首先明確幾個知識點。
1).棧
首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從記憶體高地址->低地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中
---> 暫存器ebp(base pointer )可稱為“幀指標”或“基址指標”,其實語意是相同的。
---> 暫存器esp(stack pointer)可稱為“ 棧指標”。
要知道的是:
--->ebp 在未受改變之前始終指向棧幀的開始,也就是棧底,所以ebp的用途是在堆疊中定址用的。
—>esp是會隨著資料的入棧和出棧移動的,也就是說,esp始終指向棧頂。
2).
假設函式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呼叫前的狀態。
如下圖所示
三.在明確了這些知識之後,讓我們返回上面那個簡單的函式。
1).首先來看看我畫出的圖。
上面的圖片能夠粗略的表現函式呼叫的過程。
2).
所產生的彙編程式碼:
上面兩幅圖片是mian函式的棧幀。
上面的圖片是add函式的棧幀。
3).在liunx平臺下的彙編程式碼
以上為本人在學習函式在記憶體中的操作的一些經驗,當然,本人才疏學淺,上述肯定會有紕漏,若有讀者發現,請聯絡我及時改正。謝謝!