函式的呼叫過程詳解———棧幀的建立和銷燬
●回顧內容:
函式的定義:函式是一個程式中的部分程式碼,由一個或多個語句組成,它的功能是實現某些特定的任務。函式相對於其他程式碼來說具備相對的獨立性。
函式的呼叫:在某個函式內部,使用另一個函式來完成相關的任務,這個過程叫做函式呼叫。
那麼函式是如何呼叫的呢?分析一段簡單的程式碼:
#include<stdio.h> Add(int x,int y) { int z=0; z = x+y; return z; } int main() { int a=10; int b=20; int ret=0; ret = Add(a,b); printf("%d\n",ret); return 0; }
除錯程式時檢視呼叫堆疊:
可以看出main函式其實是在mainCRTStartup函式中呼叫的。
(在VS編譯器中可以看出其實main函式是在_tmainCRTStartup函式中呼叫的,而_tmainCRTStartup函式是在mainCRTStartup中 呼叫的。)
1》每一次函式的呼叫都是一個過程,這個過程叫做函式的呼叫過程。
2》在呼叫函式的過程中,要為函式開闢棧空間,這塊空間叫做函式棧幀。棧的使用方向是由高地址向低地址使用的。
3》為了維護棧幀,需要用到ebp和esp兩個暫存器。在函式呼叫過程中ebp存放維護這個棧幀的棧底指標,esp存放棧頂指標
4》main函式的棧幀維護:
在呼叫main函式時,編譯器會先呼叫mainCRTStartup函式,在呼叫時編譯器會主動為mainCRTStartup函式開闢一塊棧幀,然後用ebp和esp分別指向這塊棧幀的棧頂和棧底。
●為main函式的呼叫做好準備工作後,對應到彙編程式碼來研究函式的呼叫過程
1.main函式棧幀的建立:要呼叫main函式就要為main函式建立棧幀
main函式棧幀的建立過程:
2.Add函式的呼叫過程:
1》
值得注意的是,在呼叫Add函式之前:
1>編譯器先為區域性變數a和b建立了一份臨時拷貝,相當於函式的傳參;
2>呼叫call指令,裡面存了call指令下一條指令的地址。這樣可以儲存main函式棧幀的狀態,也方便呼叫完Add之後返回main函式的棧幀。這可以認為是現場保護和現場恢復。
2》當彙編程式碼執行到call指令時,開啟記憶體,按F11進入函式,在監視視窗會發現call指令裡存了call指令下一條指令的地址,此時彙編程式碼來到了這裡
3》然後再按F11,就進入了Add函式的執行程式碼處,現在就開始了Add真正的呼叫過程
3.Add函式呼叫結束,銷燬Add函式的函式棧幀並返回main函式
執行完ret,彙編程式碼跳轉到剛才call指令的下一條指令的地址,
4.列印ret的值,main結束之後銷燬main函式的棧幀
程式碼執行到這裡,整個函式的呼叫已經全部結束,簡單來說,函式的呼叫過程就是函式棧幀的建立和銷燬的過程。上面的程式碼內容我是用vc6.0演示的,不同的編譯器還是有些差異,但是思想都是一致的。