1. 程式人生 > >C&C++函式呼叫過程

C&C++函式呼叫過程

呼叫函式主要關注三個方面分別是函式名,返回值和引數列表,我接下來將會深入底層講解呼叫函式的過程。

呼叫函式的過程主要有四方面,①函式引數代入,②函式棧幀開闢,③函式返回值,④函式棧幀回退

首先來看一段簡單的c檔案程式碼,和它的彙編碼,只需簡單瀏覽即可:

原始碼:

int fun1(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int a = fun1(10, 20);

	return 0;
}

 彙編碼:

main函式

int main()
{
00C31410  push        ebp  
00C31411  mov         ebp,esp  
00C31413  sub         esp,0CCh  
00C31419  push        ebx  
00C3141A  push        esi  
00C3141B  push        edi  
00C3141C  lea         edi,[ebp-0CCh]  
00C31422  mov         ecx,33h  
00C31427  mov         eax,0CCCCCCCCh  
00C3142C  rep stos    dword ptr es:[edi]  
	int a = fun1(10, 20);
00C3142E  push        14h  
00C31430  push        0Ah  
00C31432  call        fun1 (0C31127h)  
00C31437  add         esp,8  
00C3143A  mov         dword ptr [a],eax  

	return 0;
00C3143D  xor         eax,eax  
}
00C3143F  pop         edi  
00C31440  pop         esi  
00C31441  pop         ebx  
00C31442  add         esp,0CCh  
00C31448  cmp         ebp,esp  
00C3144A  call        __RTC_CheckEsp (0C3113Bh)  
00C3144F  mov         esp,ebp  
00C31451  pop         ebp  
00C31452  ret  

fun1函式

int fun1(int a, int b)
{
009813D0  push        ebp  
009813D1  mov         ebp,esp  
009813D3  sub         esp,0CCh  
009813D9  push        ebx  
009813DA  push        esi  
009813DB  push        edi  
009813DC  lea         edi,[ebp-0CCh]  
009813E2  mov         ecx,33h  
009813E7  mov         eax,0CCCCCCCCh  
009813EC  rep stos    dword ptr es:[edi]  
	int c = a + b;
009813EE  mov         eax,dword ptr [a]  
009813F1  add         eax,dword ptr [b]  
009813F4  mov         dword ptr [c],eax  
	return c;
009813F7  mov         eax,dword ptr [c]  
}
009813FA  pop         edi  
009813FB  pop         esi  
009813FC  pop         ebx  
009813FD  mov         esp,ebp  
009813FF  pop         ebp  
00981400  ret  

首先知道ebp為棧底暫存器,esp為棧頂暫存器。push為操作,操作方式為在esp中的棧頂存放資料,棧頂上移。

在檢視fun函式引數如何代入之前,我們先看一下main的棧頂和棧底

(1)函式引數代入

兩次push後,引數就被放在了main函式的棧頂,且入棧為從右往左,效果如下:

(2)fun函式棧幀開闢

 

 可以看到,call就相當於呼叫函式,這裡重新設定了棧底ebp和棧頂esp,過程如下:

檢視fun函式的開闢棧幀的過程會發現它與main函式的開闢及其類似,其實,main函式的下面也有主函式引數,也就是可以把main函式也看做為一個普通函式。

(3)函式返回

利用暫存器帶回,將暫存器的值寫入接收返回值的常量

(4)棧幀回退

將fun的棧頂指向fun的棧底:

mian函式中:esp移動8位,消除引數

現在就回到了main函式的棧頂。

(5)其他問題

①因為引數大小不同,8位元組及以上的引數採用的是提前在棧頂開闢記憶體,以儲存大位元組的引數。

②返回8位元組及以上的返回值也是採用提前開闢記憶體的方法。

③有三種不同的約定呼叫方式,分別為__cedel、__stdcall、__fastcall,這三種方式有一些細節的不同,但是思想相同,本文講的是__cedel方式。