函數調用另一個詞語表示叫作 過程。一個過程調用包括將 數據(以過程參數和返回值的形式)和 控制從代碼的一部分傳遞到另一部分。另外,它還必須在進入時為過程的局部變量分配空間,並在退出時釋放這些空間。
大多數機器,包括IA32,只提供轉移控制到過程和從過程中轉移出控制這種簡單的指令。
數據傳遞、局部變量的分配和釋放通過操縱程序棧來實現。
在了解本文章之前,您需要先對程序的進程空間有所了解,即對進程如何使用內存?如果你知道這些,下面的內 容將是很easy的事情了。為了您的回顧還是將簡單的分布圖貼出來,便於您的回顧。
我們先來了解一個概念,棧幀(stack frame),機器用棧來
傳遞過程參數,存儲返回信息,保存寄存器用於以后恢復,以及本地存儲。為單個過程(函數調用)分配的那部分棧稱為棧幀。
棧幀其實 是兩個指針寄存器,寄存器%ebp為幀指針(指向該棧幀的最底部),而寄存器%esp為棧指針(指向該棧幀的最頂部),當程序運行時,棧指針可以移動(大多數的信息的訪問都是通過幀指針的,換句話說,就是如果該棧存在,%ebp幀指針是不移動的,訪問棧里面的元素可以用-4(%ebp)或者8(%ebp)訪問%ebp指針下面或者上面的元素)。總之簡單 一句話,棧幀的主要作用是用來控制和保存一個過程的所有信息的。棧幀結構如下所示:

此處注意:這里面有一個錯誤,即:“保存的寄存器、局部變量和臨時值”處應該是ebp-4。
棧是從高地址向低地址存儲。所以越是低的地址,越是靠后入棧。
如果你已經對這個圖已經非常了解了,那么就沒有必要再看下去了。因為下面的內容都是對這幅圖的講解。
假設過程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 //save old %ebp movl %esp,%ebp //set %ebp as frame pointer pushl %ebx //save %ebx movl 8(%ebp),%edx //Get xp movl 12(%ebp),%ecx //Get yp movl (%edx),%ebx //Get x movl (%ecx),%eax //Get u movl %eax,(%edx) //Store y as xp movl %ebx,(%ecx) //Sotre x as yp addl %ebx,%eax //return value = x + y popl %ebx //restore %ebx popl %ebp //restore %ebp ret //從過程調用中返回, 將控制轉移回caller
正如前面所講的那樣,棧向低地址方向增長,而棧指針%esp指向棧頂元素,可以利用pushl將數據存入棧中並利用popl指令從棧中取出。將棧指針的值減小適當的值可以分配沒有指定初始值的數據的空間,例如:subl $24,%esp
。類似的,通過增加棧指針來釋放空間。
下面就是返回之后繼續執行的部分代碼了:
movl -4(%ebp),%edx subl -8(%ebp),%edx imull %edx,%eax //為了計算diff, leave //為返回准備棧,GCC 產生的代碼有時候會使用leave指令來釋放棧幀,
//而有時會使用一個或者兩個popl指令。兩個方法都可行。 ret //從過程調用中返回
為了計算diff,從棧中取出arg1,和arg2的值,並將寄存器%eax當做swap_add的返回值。
整個過程的棧變化如下所示:
