1 内联(inline)函数
c++在编译时可以讲调用的函数代码嵌入到主调函数中,这种嵌入到主调函数中的函数称为内联函数,又称为内嵌函数或内置函数。
- 定义内联函数时,在函数定义和函数原型声明时都使用inline,也可以只在其中一处使用,其效果一样。
- 内联函数在编译时用内联函数函数的函数体替换,所以不发生函数调用,不需要保护现场,恢复现场,节省了开销。
- 内联函数增加了目标程序的代码量。因此,一般只将函数规模很小且使用频繁的函数声明为内联函数。
- 当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理,所以内联函数内不能包含循环语句和switch语句。
内联函数格式如下:
inline 函数类型 函数名(形参列表)
{
函数体;
}
inline int add(int a, int b)
{
return a + b;
}
2 洞悉内联函数底层原理
1.使用Visual Studio 2015创建一个C++Win32控制台程序,点击项目->项目属性设置内联函数优化
2.编写内联函数代码,设置断点,debug启动
#include <iostream>
#include <string>
using namespace std;
inline int add(int a, int b)
{
return a + b;//断点1
}
int main()
{
int result = add(12, 34);
cout << result << endl;//断点2
return 0;
}
3.调试->窗口->反汇编,然后就能看到编译后的汇编程序
...
int result = add(12, 34);
00B620DE mov eax,0Ch
00B620E3 add eax,22h //对eax中和22h中值进行相加,赋值给eax
00B620E6 mov dword ptr [result],eax
cout << result << endl;
00B620E9 mov esi,esp
00B620EB push offset std::endl<char,std::char_traits<char> > (0B610A5h)
00B620F0 mov edi,esp
00B620F2 mov eax,dword ptr [result]
00B620F5 push eax
00B620F6 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)]
00B620FC call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0A8h)]
00B62102 cmp edi,esp
00B62104 call __RTC_CheckEsp (0B611C7h)
00B62109 mov ecx,eax
00B6210B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0ACh)]
00B62111 cmp esi,esp
00B62113 call __RTC_CheckEsp (0B611C7h)
return 0;
4.从汇编代码中可以代码编译后内联函数直接嵌入到主函数中,并且断点1不会执行到,下面是没使用内联函数(去掉inline关键字)的汇编代码:
int result = add(12, 34);
00291A4E push 22h
00291A50 push 0Ch
00291A52 call add (02914D8h) //调用add函数
00291A57 add esp,8//移动堆栈指针esp,继续执行主函数
00291A5A mov dword ptr [result],eax
cout << result << endl;
00291A5D mov esi,esp
00291A5F push offset std::endl<char,std::char_traits<char> > (02910A5h)
00291A64 mov edi,esp
00291A66 mov eax,dword ptr [result]
00291A69 push eax
00291A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)]
cout << result << endl;
00291A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0A8h)]
00291A76 cmp edi,esp
00291A78 call __RTC_CheckEsp (02911C7h)
00291A7D mov ecx,eax
00291A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0ACh)]
00291A85 cmp esi,esp
00291A87 call __RTC_CheckEsp (02911C7h)
system("pause");
00291A8C mov esi,esp
00291A8E push offset string "pause" (0299B30h)
00291A93 call dword ptr [__imp__system (029D1DCh)]
00291A99 add esp,4
00291A9C cmp esi,esp
00291A9E call __RTC_CheckEsp (02911C7h)
return 0;
从以上代码代码可以看出,在主函数中调用(call)了add函数。
4.在内联函数中添加几个循环后,编译器就把内联函数当做普通函数看待了,代码如下:
inline int add(int a, int b)
{
int sum = 0;
for (int i = 0; i < 100; i++)
a++;
for (int i = 0; i < 100; i++)
{
for (int i = 0; i < 100; i++)
{
sum++;
}
}
return a + b;
}
int main()
{
int result = add(12, 34);
cout << result << endl;
return 0;
}
int result = add(12, 34);
00181A4E push 22h
00181A50 push 0Ch
00181A52 call add (01814ECh) ///
00181A57 add esp,8
00181A5A mov dword ptr [result],eax
cout << result << endl;
00181A5D mov esi,esp
00181A5F push offset std::endl<char,std::char_traits<char> > (01810A5h)
00181A64 mov edi,esp
00181A66 mov eax,dword ptr [result]
00181A69 push eax
00181A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)]
cout << result << endl;
00181A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0A8h)]
00181A76 cmp edi,esp
00181A78 call __RTC_CheckEsp (01811C7h)
00181A7D mov ecx,eax
00181A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0ACh)]
00181A85 cmp esi,esp
00181A87 call __RTC_CheckEsp (01811C7h)
return 0;
00181AA3 xor eax,eax
当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理。
3 函数调用为什么会增加程序运行开销?
在程序中进行函数调用时,底层到底发生了哪些事情?程序被编译成目标程序,目标程序中指令按照顺序存放在计算机存储器中,程序运行时,有这么个指针,始终指向程序当前运行的执行语句,当调用程序时,它会指向被调函数指令的位置,调用完毕后再指回来,继续主函数执行。
在这个两次改变指向的过程中,会有这么个问题,我指向被调函数之行为,再怎么指回来,是不是需要把主函数的地址记录下来,方便指针”回家不迷路“;这个过程有一个专业的名称“保护现场”,保护现场保护的不止这一个指针变量,还有操作数、堆栈地址等,当被调函数执行完毕后,重新把这些变量放到它原来的位置,继续执行,这个过程称为“恢复现场”,保护现场和恢复现场会占用空间和时间,因而会增加程序运行开销。不过当被调函数复杂(运行时间足够长),保护现场和恢复现场的时间微不足道,编辑器就会把内联函数看成普通函数调用,缩短了主函数指令量,主函数指令量太多,又会涉及到页面置换的问题,如果有想比较更深层次第理解,可以参考计算机组成原理和操作系统的相关知识。