標籤:
這篇blog試圖說明這麼一個問題,當一個c函數被調用時,一個棧幀(stack frame)是如何被建立,又如何被消除的。這些細節跟作業系統平台及編譯器的實現有關,下面的描述是針對運行在Linux的gcc編譯器而言的。c語言的標準並沒有描述實現的方式。所以,不同的編譯器、不同的作業系統都可能有自己的建立棧幀的方式。
下面先看一個典型的棧幀:
上面個的這個圖是一個典型的棧幀,圖中,棧頂在上,地址空間往下增長。
在看看這個棧對應的函數代碼:
int foo(int arg1, int arg2, int arg3);
並且假設foo有兩個局部的int變數(各佔4個位元組).
在上面的棧幀對應的情境中,main調用foo,而程式的控制仍在foo中。這裡,main是調用者(caller),foo是被調用者(callee)。
ESP被foo使用來指示棧頂,EBP相當於一個“基準指標”。從main傳遞到foo的參數以及foo本身的局部變數都可以通過這個基準指標為參考,加上位移量找到。
由於被調用者允許使用EAX、ECX和EDX寄存器,所以如果調用者希望儲存這些寄存器的值,就必須在調用子函數之前顯式地把它們儲存在棧中。另一方面,如果除了上面提到的幾個寄存器,被調用者還想使用別的寄存器,比如EBX、ESI和EDI,那麼,被調用者就必須在棧中儲存這些被額外允許使用的寄存器,並在調用返回前恢複它們。也就是說,如果被調用者只使用約定的EAX、ECX和EDX寄存器,它們由調用者負責儲存並恢複,但是如果被調用者還額外的使用了其他的寄存器,則必須有被調用者自己儲存並恢複這些寄存器的值。
傳遞給foo的參數被壓到棧中,最後一個參數先進棧,所以第一個參數是位於棧頂的。foo中聲明的局部變數以及函數執行過程中需要用到的一些臨時變數也都存在棧中。
小於等於4個位元組的傳回值會被儲存到EAX寄存器中,如果大於4位元組,小於8位元組,那麼EDX也會被用來儲存傳回值。如果傳回值佔用的控制項還要大,那麼調用者會向被調用者傳遞一個額外的參數,這個額外的參數指向將要儲存傳回值的地址。用c語言來說,就是函數調用:
x = foo(a, b, c);
被轉化為:
foo(&x, a, b, c);
注意,這僅僅在傳回值佔用大於8個位元組時才發生。有的編譯器不用EDX儲存傳回值,所以當傳回值大於4個位元組時,就用這種轉換。
http://www.cnblogs.com/dahai/archive/2011/07/29/2120651.html
http://www.cnblogs.com/DylanWind/archive/2008/12/08/1349822.html
http://www.cnblogs.com/shitouer/archive/2010/04/05/1704554.html
http://johnlxj.diandian.com/post/2011-03-20/16690291
http://www.cnblogs.com/dolphin0520/archive/2011/04/04/2005061.html
http://blog.chinaunix.net/uid-29584360-id-4207364.html
http://blog.csdn.net/zsy2020314/article/details/9429707
http://www.360doc.com/content/12/1009/13/1317564_240416017.shtml
C函數調用與棧