函數的調用過程,棧桢的建立和銷毀
我們首先要知道關于函數的調用,我們知道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函數處,