函數調用過程棧幀變化詳解


函數調用另一個詞語表示叫作 過程。一個過程調用包括將數據和控制從代碼的一部分傳遞到另一部分。另外,它還必須在進入時為過程的局部變量分配空間,並在推出時釋放這些空間。而數據傳遞,局部變量的分配和釋放通過操縱程序棧來實現。在了解本文章之前,您需要先對程序的進程空間有所了解,即對進程如何使用內存?如果你知道這些,下面的內容將是很easy的事情了。為了您的回顧還是將簡單的分布圖貼出來,便於您的回顧。

我們先來了解一個概念,棧幀(stack frame),機器用棧來傳遞過程參數,存儲返回信息,保存寄存器用於以后恢復,以及本地存儲。為單個過程(函數調用)分配的那部分棧稱為棧幀。棧幀其實是兩個指針寄存器,寄存器%ebp為幀指針,而寄存器%esp為棧指針,當程序運行時,棧指針可以移動(大多數的信息的訪問都是通過幀指針的)。總之簡單一句話,棧幀的主要作用是用來控制和保存一個過程的所有信息的。棧幀結構如下所示:

  

如果你已經對這個圖已經非常了解了,那么就沒有必要再看下去了。因為下面的內容都是對這幅圖的講解。

  假設過程P(調用者)調用過程Q(被調用者),則Q的參數放在P的棧幀中。另外,當P調用Q時,P中的返回地址被壓入棧中,形成P的棧幀的末尾(返回地址就是當程序從Q返回時應該繼續執行的地方)。Q的棧幀從保存的幀指針的值開始,后面到新的棧指針之間就是該過程的部分了。

  過程實例講解:

下面以這個程序為例進行簡要說明函數調用的基本過程。

int swap_add(int* xp,int* yp) {
    int x = *xp;
    int y = *yp;
    *xp = y;
    *yp = x;
    return x+y;
}
int caller(){
    int arg1 = 534;
    int arg2 = 1057;
    int sum = swap_add(&arg1,&arg2);
    int diff = arg1 - arg2;
    
    return sum * diff;
}

經過匯編之后caller部分的代碼如下:

caller:
    pushl %ebp   //保存%ebp
    movl %esp,%ebp    //設置新的幀指針為舊的棧指針
    subl $24,%esp  //分配24子節的棧空間
    movl $534,-4(%ebp) //設置arg1=534
    movl $1057,-8(%ebp) //設置arg2=1057
    leal -8(%ebp),%eax //計算&arg2
    movl %eax,4(%esp) //將&arg2存入棧中
    leal -4(%ebp),%eax //計算&arg1
    movl %eax,(%esp) //將&arg1存入棧中
    call swap_add //調用swap_add

這段代碼先保存了%ebp的一個副本,將新的過程(該函數的ebp)的ebp設置為棧幀的開始位置。然后將棧指針減去24,從而在棧上分配了24字節的空間(你應該思考一下為什么是24字節),然后是初始化兩個局部變量,計算兩個局部變量的地址並存入棧中,形成了函數swap_add的參數。將這些參數存儲到相對於棧指針偏移量為0和+4的地方,留待稍后的swap_add調用訪問。然后調用swap_add.

接下的代碼是swap_add的函數部分:

swap_add:
	pushl %ebp
	movl %esp,%ebp
	pushl %ebx
	
	movl 8(%ebp),%edx
	movl 12(%ebp),%ecx
	movl (%edx),%ebx
	movl (%ecx),%eax
	movl %eax,(%edx)
	movl %ebx,(%ecx)
	addl %ebx,%eax
	
	popl %ebx
	popl %ebp
	ret
	

代碼分為3部分 建立部分:初始化棧幀;主體部分:執行過程的實體計算;結束部分:回復棧幀的狀態,以及過程返回。這一部分的代碼比較簡單,就不在一一介紹,根據以上的3部分,划分的已經很清晰了。(說明一點程序在執行到swap_add的代碼之前,也就是在執行call語句已經把返回地址壓入棧中)值得注意的是最后一部分的popl %ebx   popl %ebp。它的作用是恢復了之前存儲的棧幀指針的值,也就是調用程序的原始棧幀指針。從而程序就可以得到返回(有些細心的人會問那返回值咋么辦?呵呵,返回值是存入了%eax中,在接下來的調用程序caller中直接訪問該寄存器就可以了)。

下面就是返回之后繼續執行的部分代碼了:

movl -4(%ebp),%edx
subl -8(%ebp),%edx
imull %edx,%eax
leave
ret

為了計算diff,從棧中取出arg1,和arg2的值,並將寄存器%eax當做swap_add的返回值。

整個過程的棧變化如下所示:

 

推薦一篇對棧幀的講解不錯的文章:http://blog.csdn.net/yxysdcl/article/details/5569351

參考文獻:《深入理解計算機系統》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM