1. 程式人生 > >函式呼叫時的開銷

函式呼叫時的開銷

問題引入

在學習C語言時,老師強調過呼叫函式時會有開銷,但是函式呼叫的開銷體現在哪幾個方面並不十分清楚!!

舉例說明

寫一個兩數求和的程式碼,此程式碼中不呼叫函式

#include <stdio.h>
int main()
{
    int a = 10, b = 20, c = 0;
    c = a + b;
    printf("%d\n", c);
    return 0;
}

該程式對應的反彙編如下:

#include <stdio.h>
int main()
{
00BA13C0  push        ebp  
00BA13C1  mov
ebp,esp 00BA13C3 sub esp,0E4h 00BA13C9 push ebx 00BA13CA push esi 00BA13CB push edi 00BA13CC lea edi,[ebp-0E4h] 00BA13D2 mov ecx,39h 00BA13D7 mov eax,0CCCCCCCCh 00BA13DC rep stos dword ptr es:[edi] int a = 10, b = 20, c = 0; 00BA13DE mov
dword ptr [a],0Ah 00BA13E5 mov dword ptr [b],14h 00BA13EC mov dword ptr [c],0 c = a + b; 00BA13F3 mov eax,dword ptr [a] 00BA13F6 add eax,dword ptr [b] 00BA13F9 mov dword ptr [c],eax printf("%d\n", c); 00BA13FC mov esi,esp 00BA13FE mov
eax,dword ptr [c] 00BA1401 push eax 00BA1402 push 0BA5858h 00BA1407 call dword ptr ds:[0BA9114h] 00BA140D add esp,8 00BA1410 cmp esi,esp 00BA1412 call __RTC_CheckEsp (0BA1136h) return 0; 00BA1417 xor eax,eax } 00BA1419 pop edi 00BA141A pop esi 00BA141B pop ebx 00BA141C add esp,0E4h 00BA1422 cmp ebp,esp 00BA1424 call __RTC_CheckEsp (0BA1136h) 00BA1429 mov esp,ebp 00BA142B pop ebp 00BA142C ret

再寫一個同樣功能的程式,與之前不同的是該函式中有函式呼叫

#include <stdio.h>
int ADD(int x, int y)
{
    return x + y;
}
int main()
{
    int a = 10, b = 20, c = 0;
    c = ADD(a, b);
    printf("%d\n", c);
    return 0;
}

該函式對應的反彙編如下所示:

int main()
{
00051A00  push        ebp  
00051A01  mov         ebp,esp  
00051A03  sub         esp,0E4h  
00051A09  push        ebx  
00051A0A  push        esi  
00051A0B  push        edi  
int main()
{
00051A0C  lea         edi,[ebp-0E4h]  
00051A12  mov         ecx,39h  
00051A17  mov         eax,0CCCCCCCCh  
00051A1C  rep stos    dword ptr es:[edi]  
    int a = 10, b = 20, c = 0;
00051A1E  mov         dword ptr [a],0Ah  
00051A25  mov         dword ptr [b],14h  
00051A2C  mov         dword ptr [c],0  
    c = ADD(a, b);
00051A33  mov         eax,dword ptr [b]  
00051A36  push        eax  
00051A37  mov         ecx,dword ptr [a]  
00051A3A  push        ecx  
00051A3B  call        ADD (0511DBh)  
00051A40  add         esp,8  
00051A43  mov         dword ptr [c],eax  
    printf("%d\n", c);
00051A46  mov         esi,esp  
00051A48  mov         eax,dword ptr [c]  
00051A4B  push        eax  
00051A4C  push        55858h  
00051A51  call        dword ptr ds:[59114h]  
00051A57  add         esp,8  
00051A5A  cmp         esi,esp  
00051A5C  call        __RTC_CheckEsp (051136h)  
    return 0;
00051A61  xor         eax,eax  
}
00051A63  pop         edi  
00051A64  pop         esi  
00051A65  pop         ebx  
00051A66  add         esp,0E4h  
00051A6C  cmp         ebp,esp  
00051A6E  call        __RTC_CheckEsp (051136h)  
00051A73  mov         esp,ebp  
00051A75  pop         ebp  
00051A76  ret  

對比以上兩段程式碼的反彙編,可以發現有如下不同
沒有呼叫函式

c = a + b;
00BA13F3  mov         eax,dword ptr [a]  
00BA13F6  add         eax,dword ptr [b]  
00BA13F9  mov         dword ptr [c],eax  

呼叫函式

c = ADD(a, b);
00051A33  mov         eax,dword ptr [b]  
00051A36  push        eax  
00051A37  mov         ecx,dword ptr [a]  
00051A3A  push        ecx  
00051A3B  call        ADD (0511DBh)  
00051A40  add         esp,8  
00051A43  mov         dword ptr [c],eax  

由以上區別可知函式呼叫的開銷在於引數的壓棧過程push、和函式的呼叫call。

幾點說明

由於我測試的環境是VS2013,很可能是編譯器對程式的執行過程進行了優化,一般來說函式的開銷有以下幾個方面:
1、將引數壓入棧中,引數越多,開銷越大
2
、將控制權轉移至函式中
3
、建立新的棧幀,也就是當前函式使用的“一片”棧空間,使用ebp的值來標識新的棧幀,因此要將原棧幀首地址儲存下來,方便回到原來的即呼叫者的棧幀
4
、恢復原棧幀,然後將控制權轉移至呼叫者

函式雖然有一定開銷,但是在該使用函式的時候還是要使用函式,只用當某個函式規模較小並且呼叫的次數比較頻繁時,就將該函式用巨集代替。