函式棧幀(呼叫過程)

audience_fzn發表於2018-08-05

函式棧幀就是在呼叫函式是為其在棧空間上開闢了一段空間,指向過程呼叫,一個過程呼叫包括將資料(以過程引數和返回值的形式)和控制從程式碼的一部分傳遞到另一部分。

  • 棧是向下生長的,從高地址到低地址延伸的
  • 每個函式的每次呼叫的過程,都有它自己獨立的一個棧幀結構,用於變數的儲存,現場的保護
  • 要維護這個棧幀必須使用ebp(棧底指標)和esp(棧頂指標)這倆個暫存器
  • cpu如何知道函式執行到哪裡——暫存器存放著函式當前正在執行指令的下一條指令

我們以以下程式碼為例講解整個函式呼叫過程:

int my_add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 0xaaaaaaaa;
	int b = 0xbbbbbbbb;
	int ret = my_add(a, b);
	printf("you shuold running here!\n");
	system("pause");
	return 0;
}

一、呼叫main()函式:

我們從main()函式的地方開始,要展開main()函式的呼叫就得為main()函式建立棧幀。

  • main()函式不是程式的第一個入口,它被start函式呼叫
  • 函式的棧幀被釋放,並不是將其內容清空,而是做了一個標記,說明此空間裡面的資料無效了。當又有函式被呼叫形成棧幀是,直接覆蓋掉這塊空間就好了

 

  1. 將當前執行的指令的下一條指令的地址壓入Start函式的棧中
  2. strat函式呼叫main()函式,把ebp先壓棧,用來儲存strat函式ebp佔地指標的位置
  3. 將strat函式的esp的值賦給ebp,也就是讓ebp作為將要開闢的main函式的棧底
  4. 讓ebp減去某個值,這是在為main函式棧幀開闢空間
  5. 將mian函式中的變數依次壓入棧中

二、呼叫my_add函式:

 

  • mian函式呼叫my_add函式,形參例項化(形參列表從右向左壓棧)
  • 執行call命令,將當前正在執行的指令的下一條指令壓入棧中,並跳轉到指定函式
  • 將main函式的ebp壓棧,用來儲存main函式的ebp指標位置

三、my_add函式棧幀的建立

  • 將main的esp賦值給ebp,讓ebp作為my_add函式的棧底
  • 讓esp減去某個值,讓他指向另一個位置,即my_add函式的棧頂
  • 將my_add函式中的變數壓棧(按先後順序)

 

四、my_add函式呼叫完畢返回main函式

  • my_add函式的返回值通過eap暫存器返回
  • 將my_add函式的esp指向ebp,即銷燬了my_add函式的棧幀
  • pop my_add函式ebp,恢復到原來main的ebp(在呼叫之前儲存了main()函式的ebp,將這個值賦予ebp),使得ebp重新指向main函式的棧底
  • 同時從main函式棧頂彈出esp當前指向的內容(下一條指令的地址)帶著my_add的返回值,取找下一條指令
  • my_add函式呼叫結束,初始化的引數也隨之失效,通過移動esp指標將其移除

五、main函式的返回

 

  • 暫存器出棧
  • 將ebp賦給esp,esp和ebp同時指向棧底(也是start函式的棧頂),釋放了mian函式的棧幀
  • 將ebp出棧,恢復到start函式的ebp,使得ebp指向strat函式的棧底

 

總結:

1.函式呼叫過程中會形成臨時變數(形參例項化)在呼叫函式和被呼叫函式的棧幀之間

2.形參例項化的順序,從右往左

3.臨時變數為什麼具有臨時性?

函式呼叫時形成棧幀,返回時銷燬棧幀,而定義的變數在自己的棧幀中,所以臨時變數的生命週期伴隨著自己的棧幀結構

4.每一個函式都有自己的棧幀,由自己產生(修改ebp,esp)

函式通過修改ebp,esp形成自己的棧幀

5.常規情況下,函式的返回值都會以暫存器的形式返回

總結:棧幀的主要作用是用來控制和儲存一個函式呼叫的所有資訊的。

 

 

相關文章