天天看點

函數的調用過程,棧桢的建立和銷毀。

                 函數的調用過程,棧桢的建立和銷毀

我們首先要知道關于函數的調用,我們知道main函數也是要被調用的,在_tmainCRTStartup函數中調用,而_tmainCRTStartup函數是在mainCRTStartup中被調用的。

函數調用過程要為函數開辟棧空間,用于本次函數調用中臨時變量的儲存,稱為函數棧桢。

下面我用一個簡單的程式說明函數的調用過程:

#include<stdio.h>
#include<string>
int Add(int x, int y)
{
  int sum = 0;
  sum = x + y;
  return sum;
}
int main()
{
  int a = 1;
  int b = 2;
  int ret = 0;
  ret = Add(a, b);
  printf("%d", ret);
  system("pause");
  return 0;
}      

在這個程式裡,主函數定義了3個局部變量,然後調用了Add函數。三個局部變量都在棧空間上存放。

可是當程式運作起來後,main函數是如何實作對Add函數的調用過程呢?我們轉到反彙編一步步分析一下。

下面是main函數的彙編代碼(在VS2013平台下):

int main()
{
009253D0  push        ebp  
009253D1  mov         ebp,esp  
009253D3  sub         esp,0E4h  
009253D9  push        ebx  
009253DA  push        esi  
009253DB  push        edi  
009253DC  lea         edi,[ebp-0E4h]  
009253E2  mov         ecx,39h  
009253E7  mov         eax,0CCCCCCCCh  
009253EC  rep stos    dword ptr es:[edi]  
  int a = 1;
009253EE  mov         dword ptr [a],1  
  int b = 2;
009253F5  mov         dword ptr [b],2  
  int ret = 0;
009253FC  mov         dword ptr [ret],0  
  ret = Add(a, b);
00925403  mov         eax,dword ptr [b]  
00925406  push        eax  
00925407  mov         ecx,dword ptr [a]  
0092540A  push        ecx  
0092540B  call        Add (0921113h)  
00925410  add         esp,8  
00925413  mov         dword ptr [ret],eax  
  printf("%d", ret);
00925416  mov         esi,esp  
00925418  mov         eax,dword ptr [ret] 
0092541B  push        eax  
0092541C  push        92CC7Ch  
00925421  call        dword ptr ds:[93019Ch]  
00925427  add         esp,8  
0092542A  cmp         esi,esp  
0092542C  call        __RTC_CheckEsp (09212E4h)  
  system("pause");
00925431  mov         esi,esp  
00925433  push        92CD34h  
00925438  call        dword ptr ds:[930184h]  
0092543E  add         esp,4  
00925441  cmp         esi,esp  
00925443  call        __RTC_CheckEsp (09212E4h)  
  return 0;
00925448  xor         eax,eax  
}
0092544A  pop         edi  
}
0092544B  pop         esi  
0092544C  pop         ebx  
0092544D  add         esp,0E4h  
00925453  cmp         ebp,esp  
00925455  call        __RTC_CheckEsp (09212E4h)  
0092545A  mov         esp,ebp  
0092545C  pop         ebp  
0092545D  ret      

1、從main函數的調用開始,需要為main函數建立函數棧桢。

int main()
{
009253D0  push        ebp       //将ebp壓入棧底
009253D1  mov         ebp,esp   //将esp的值賦給ebp,得到新的ebp
009253D3  sub         esp,0E4h   //将esp的值減去0E4h,得到新的esp
009253D9  push        ebx        //從低位址到高位址三次壓棧壓入三個寄存器ebx、
009253DA  push        esi       //esi、                                                                                          009253DB  push        edi       //edi
009253DC  lea         edi,[ebp-0E4h]  //加載有效位址
009253E2  mov         ecx,39h           //将加載到edi的位址重複拷貝ecx次,
009253E7  mov         eax,0CCCCCCCCh    //即把main函數預開辟的空間全部初始化為0CCCCCCCCh
009253EC  rep stos    dword ptr es:[edi]  //重複拷貝
  int a = 1;
009253EE  mov         dword ptr [a],1   //處理局部變量a,把1賦給a
  int b = 2;
009253F5  mov         dword ptr [b],2  // 處理局部變量b,把2賦給b
  int ret = 0;
009253FC  mov         dword ptr [ret],0  
  ret = Add(a, b);
00925403  mov         eax,dword ptr [b]  
00925406  push        eax  
00925407  mov         ecx      

初始化如下,調試可知:

函數的調用過程,棧桢的建立和銷毀。

2、Add函數的調用,參數傳遞過程如下:

int ret = 0;
011C540C  mov         dword ptr [ret],0   //ret的初始化
  ret = Add(a, b);
011C5413  mov         eax,dword ptr [b]   //将b的值賦給eax,壓棧eax,形參b的執行個體化
011C5416  push        eax  
011C5417  mov         ecx,dword ptr [a]    //将a的值賦給ecx,壓棧ecx,形參a的執行個體化
011C541A  push        ecx  
011C541B  call        Add (011C1113h)     //調用,壓棧call指令的下一條指令的位址,再跳轉到Add函數的地方
011C5420  add         esp,8  
011C5423  mov         dword ptr [ret],eax  
  printf("%d", ret);      

繼續調試,進入Add函數:

#include<stdio.h>
#include<string>
int Add(int x, int y)
{
011C3860  push        ebp             //壓入ebp
011C3861  mov         ebp,esp          //将esp的值賦給ebp
011C3863  sub         esp,0CCh        //将esp的值減去0CCh
011C3869  push        ebx  
011C386A  push        esi  
011C386B  push        edi  
011C386C  lea         edi,[ebp-0CCh]   //加載有效位址
011C3872  mov         ecx,33h  
011C3877  mov         eax,0CCCCCCCCh    //初始化開辟的空間
011C387C  rep stos    dword ptr es:[edi]    //重複拷貝
  int sum = 0;
011C387E  mov         dword ptr [sum],0    //建立sum,并初始化
  sum = x + y;
011C3885  mov         eax,dword ptr [x]     //将形參x的值賦給eax
011C3888  add         eax,dword ptr [y]    //将形參y的值賦給eax
011C388B  mov         dword ptr [sum],eax    //将相加後的值存儲到sum中
  return sum;
011C388E  mov         eax,dword ptr [sum]   //将sum的值賦給eax,即将結果存儲到寄存器中
}
011C3891  pop         edi            //出棧
011C3892  pop         esi            //出棧 
011C3893  pop         ebx            //出棧                                                                                      011C3894  mov         esp,ebp        //将ebp的值賦給esp,使esp向下移動
011C3896  pop         ebp            //出棧,将出棧的内容儲存在ebp中,回到main的棧桢
011C3897  ret                        //出棧一次,并将出棧的内容當做位址,将程式執行跳轉到該位址處      

跳轉到Add函數處,

函數的調用過程,棧桢的建立和銷毀。