函式是我們c語言中經常接觸到的乙個內容。我們的程式設計和**都會有函式。那麼函式的呼叫有是怎樣的乙個過程呢?這個過程在記憶體中又是如何來實現呢?這些問題的答案就是函式的呼叫,這個呼叫的過程中要為函式開闢棧空間,這塊空間就是函式棧幀。
我在學習了函式的呼叫和棧幀之後,深感這一知識的重要和理解的困難,所以寫了這篇關於函式棧幀的部落格。其中有理解的不清楚或不對的地方也請大家指正。
以下是我這次理解函式棧幀時所用的**,是乙個十分簡單的add加法函式。
(注意,我用的程式設計工具是visual studio 2013)
//add函式的棧幀
#include int add(int x, int y)
int main()
將以上**進行除錯,「除錯--視窗--反彙編」進入反彙編視窗,則介面如下
上面這張中的11--12行之間的反彙編**的作用是為main函式做準備工作。
1.
2.
「00901411 mov ebp,esp 」:將esp的值賦給了ebp,產生了新的ebp。
3.
「00901413 sub esp,0e4h 」;將esp減去乙個16進製制的數字,產生了新的esp。(esp為存放了指向函式棧幀棧頂的位址。)
(注意:棧頂的位址經常會發生變化,這是因為函式的棧幀就是經常變化的。而棧幀在開闢新空間時是在棧頂進行的,所以此時esp也會隨之變化。)
4.
「00901419 push ebx 「
」0090141a push esi「 :這三條**指令將ebx,esi,edi壓棧。(注意:此時隨著它們的壓入,esp也要隨之上移)
」0090141b push edi」
5.
」0090141c lea edi,[ebp-0e4h] 」 :載入有效位址。
「0901422 mov ecx,39h 「 :給ecx賦值。
「00901427 mov eax,0cccccccch」:給eax賦值。
」0090142c rep stos dword ptr es:[edi]「 :進行重複儲存,儲存內容為:「ecx」個「eax」。
(以上這四條指令的實質為:把棧幀開闢的空間全部初始化為「oxcccccccc」)
只靠文字的描述可能並不直觀,下面我用圖形的方式再次進行理解。
接下來,我們直接跳到呼叫add函式時的反彙編**:
」0090143f push eax「 :將要傳遞給add函式的引數「b」賦值給暫存器eax,壓入eax。
2.
"00901440 mov ecx,dword ptr [a]"
"00901443 push ecx " :將要傳遞給add的引數「a」賦值給暫存器ecx,壓入ecx。
3.
「00901444 call _add (0901127h)」 :call指令的呼叫,要先進行壓棧。
4.
"00901449 add esp,8 " :這裡得到了call指令下一條指令的位址,然後跳轉到add函式的位址。(注意:這個位址十分重要,有了這個位址,在函式呼叫結束後才可以返回到這裡繼續執行指令。)
5.
「0090144c mov dword ptr [ret],eax」 :將add函式的返回值賦給ret。
我們在上面已經知道了函式的初始化和函式中再呼叫乙個函式的過程是如何進行的,接下來再讓我們看一下函式的返回過程:
「009013ee mov eax,dword ptr [z]」 :將函式的返回值z賦值給暫存器eax。
2.
「009013f1 pop edi「
「009013f2 pop esi」 :將edi,esi,ebx逐次進行出棧。(注意:此時棧頂變化,esp也要隨之發生變化。)
」009013f3 pop ebx「
3.
」009013f4 mov esp,ebp「 :將ebp賦值給esp。
4.
」009013f6 pop ebp「 :出棧,將出棧的內容儲存到ebp回到main函式的棧幀。
5.
下面,用圖形來模擬這一過程:
以上就是函式的呼叫過程(棧幀)。
函式呼叫過程(棧幀)
眾所周知,程式每呼叫乙個函式,系統都會為其開闢一塊空間,當它返回時,才收回這塊空間。程式崩潰有一部分原因就是因為無限制的呼叫函式,卻沒有及時返回,導致記憶體空間不夠。為了更好的維護這一塊空間 通常稱為棧空間 我們需要了解兩個暫存器,乙個為 esp 指向棧頂的指標 乙個為 ebp 指向棧底的指標 棧空...
函式棧幀(呼叫過程)
函式棧幀就是在呼叫函式是為其在棧空間上開闢了一段空間,指向過程呼叫,乙個過程呼叫包括將資料 以過程引數和返回值的形式 和控制從 的一部分傳遞到另一部分。我們以以下 為例講解整個函式呼叫過程 int my add int x,int y int main 一 呼叫main 函式 我們從main 函式的...
函式的呼叫過程 棧幀
在談棧幀之前,我們必須要先知道c c 程式記憶體的分配情況。乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放為執行函式而分配的局 部變數 函式引數 返回資料 返回位址等。其操作方式類似於資料結構中的 棧。2 堆區 heap 一般由程式設計師分配釋...