關於函數的調用過程(棧幀)

    以以下函數爲例,來了解函數的調用過程,即棧幀。

#include <stdio.h>
#include <stdlib.h>

int Add(int x, int y)
{
	int ret = 0;
	ret = x + y;
	return ret;
}

int main()
{
	int a = 10;
	int b = 20;
	int ret = 0;
	ret = Add(a, b);
	printf("%d\n", ret);
	system("pause");
	return 0;
}
    在vs2013中調試該程序,並在調試窗口轉匯編,可以得到該程序的彙編語言。

    1、在正式看彙編語言之前,我們需要先了解一下main()函數的調用。 main()函數被__tmainCRTStartup()函數調用,__tmainCRTStartup()函數被mainCRTStartup()函數調用。  所以,先爲mainCRTStartup()函數開闢空間,再爲__tmainCRTStartup()函數開闢空間,最後爲main函數開闢空間。

 【注意】1)很顯然,本文中的圖像都是上爲高地址,下爲低地址

      2)本文中省略了很多esp的變化過程,但讀者需知道,esp始終指向棧頂(出棧操作中esp會改變,但仍然只指向棧頂,被修改的部分已不在棧內)

    

    2、進入main函數的彙編語言

      保留__tmainCRTStartup()的棧底,因爲運行完main()後還要回到__tmainCRTStartup()

         改變ebp的位置



   main開闢空間,0E4h大小

 


   壓棧ebx、esi、edi

 


   加載有效地址 使edi指向ebp-0E4h處

 


   39h = 0E4h/4  39h存入ecx寄存器。四個字節爲一個單位,共39h個單位

   將隨機數存入eax寄存器

   從edi指向的位置向下重複賦值,一次2個字節,賦爲隨機值。即將棧幀開闢的空間全部初始化爲隨機值

 


   

   


   

   


   

   


   

  將b的值放入eax寄存器中,然後將其壓棧;將a的值放入ecx寄存器中,然後將其壓棧。
  用_b表示b的值,用_a表示a的值,該過程在內存中的表示如下:

 


   調用Add函數,壓棧012D3F20(即 call指令下一條指令的地址)

   暫時不用看這兩條指令,等Add函數調用完會返回此處

 


 3、進入Add()函數的彙編語言

 

 與上述過程類似,此過程在內存中的變化爲:

 


 

 // 將main函數空間中存儲的_a的值(形參)存入eax寄存器,此時eax中的值爲10
 // 將main函數空間中存儲的_b的值(形參)加入eax寄存器,此時eax中的值爲30
 // 將eax寄存器中的值(30)存入Add函數中ret局部變量中
 // 將ret局部變量的值(30)存入寄存器eax中,作爲返回值


   

 // 這段代碼是爲了銷燬Add函數空間,首先edi、esi、ebx出棧
 // 然後將ebp的值給esp,過程如下:

 

     //然後將ebp指回main函數的ebp,銷燬存儲main函數ebp的空間

 


 // 此時回到main函數中call指令的下一條指令處:

 

 // 第一條指令銷燬形參,將_a、_b空間銷燬
 // 第二條指令將eax寄存器中存儲的ret返回值(30)存入main函數空間的ret中


 至此,Add函數已全部結束,main函數也基本結束,對於printf和system(“pause”),以及return 0就不再多做解釋。

 全部過程整理到一張圖上爲:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章