c++中内联函数那点事

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 函数调用为什么会增加程序运行开销?

    在程序中进行函数调用时,底层到底发生了哪些事情?程序被编译成目标程序,目标程序中指令按照顺序存放在计算机存储器中,程序运行时,有这么个指针,始终指向程序当前运行的执行语句,当调用程序时,它会指向被调函数指令的位置,调用完毕后再指回来,继续主函数执行。
    在这个两次改变指向的过程中,会有这么个问题,我指向被调函数之行为,再怎么指回来,是不是需要把主函数的地址记录下来,方便指针”回家不迷路“;这个过程有一个专业的名称“保护现场”,保护现场保护的不止这一个指针变量,还有操作数、堆栈地址等,当被调函数执行完毕后,重新把这些变量放到它原来的位置,继续执行,这个过程称为“恢复现场”,保护现场和恢复现场会占用空间和时间,因而会增加程序运行开销。不过当被调函数复杂(运行时间足够长),保护现场和恢复现场的时间微不足道,编辑器就会把内联函数看成普通函数调用,缩短了主函数指令量,主函数指令量太多,又会涉及到页面置换的问题,如果有想比较更深层次第理解,可以参考计算机组成原理和操作系统的相关知识。

发布了33 篇原创文章 · 获赞 10 · 访问量 2049

猜你喜欢

转载自blog.csdn.net/qq_33670157/article/details/104608487