问题引入
在学习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
、恢复原栈帧,然后将控制权转移至调用者
函数虽然有一定开销,但是在该使用函数的时候还是要使用函数,只用当某个函数规模较小并且调用的次数比较频繁时,就将该函数用宏代替。