函式呼叫時的開銷
阿新 • • 發佈:2019-02-04
問題引入
在學習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
、恢復原棧幀,然後將控制權轉移至呼叫者
函式雖然有一定開銷,但是在該使用函式的時候還是要使用函式,只用當某個函式規模較小並且呼叫的次數比較頻繁時,就將該函式用巨集代替。