内联函数inline
内联参考
https://blog.csdn.net/cxy_zjt/article/details/124776420?spm=1001.2014.3001.5502
当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等, 这些工作需要系统时间和空间的开销。
函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。
call代表着函数调用,此时会存在函数的压栈等一系列操作
使用inline关键字修饰函数my_add()之后,发现汇编代码中没有了call操作,说明省去了一系列函数调用操作(压栈、跳转、退栈和返回操作等),而是在寄存器种直接替换操作,提高了效率。
内联函数inline
如何工作
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
关键字 inline
必须与函数定义体放在一起才能使函数成为内联,仅将 inline
放在函数声明前面不起任何作用。
void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
{
…
}
inline
是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。 一般地,用户可以阅读函数的声明,但是看不到函数的定义。声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。
#include <iostream>
using namespace std;
//声明内联函数
void swap1(int *a, int *b); //也可以添加inline,但编译器会忽略
int main()
{
int m, n;
cin>>m>>n;
cout<<m<<", "<<n<<endl;
swap1(&m, &n);
cout<<m<<", "<<n<<endl;
return 0;
}
//定义内联函数
inline void swap1(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
宏定义与内联函数
内联函数可以看作是宏函数的升级版本,将宏函数的优点保留,将缺点去掉。
C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在 C++ 程序中,应该用内联函数取代所有宏代码,“断言 assert” 恐怕是唯一的例外。
assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的 Debug 版本和 Release版本引起差别,assert 不应该产生任何副作用。如果 assert 是函数,由于函数调用会引起内存、代码的变动,那么将导致 Debug 版本与 Release 版本存在差异。所以 assert 不是函数,而是宏。
//宏定义的缺点
1.不方便调试宏。(因为预编译阶段进行了替换)
由于宏是预编译程序来处理,其替换的函数不会进入到符号表中,所以在运行时,不会带来额外的时间和空间开销,而函数会在运行时执行压栈出栈的操作,存在函数调用的开销。所以宏是不可以调试的,而函数可以进行单步调试。
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查。
#define MAX(a,b) (a)>(b)?(a):(b)
result = MAX(i,j)+2;
//被预处理器扩展为
result = (i)>(j)?(i):(j)+2;
//由于运算符"+"比运算符"?:"的优先级高,所以上述语句并不等价于
result = ((i)>(j)?(i):(j))+2;
如果把宏代码改写成:
#define MAX(a,b) ((a)>(b)?(a):(b))
此时可以解决优先级问题,但是:
result = MAX(i++,j);
//被预处理器扩展为
result = (i++)>(j)?(i++):(j) //在同一个表达式中i被两次求值。
宏定义与内联函数的区别
- 内联函数在编译时展开,带参的宏在预编译时展开。
- 内联函数直接嵌入到目标代码中,带参的宏是简单的做文本替换。
- 内联函数有类型检测、语法判断等功能,宏只是替换。
- 内联函数可以调试,而宏定义不可以。
-
内联函数可以访问类的成员变量,宏定义不能。
-
在类中声名同时定义的成员函数,拥有内联属性。
-
内联函数是函数,宏不是函数。
内联函数总结
内联函数是针对C语言中宏定义的优化,把这个以空间换时间的优化从预编译阶段调整到了编译阶段,所以增加了对使用了内联机制的函数的类型安全检查,或者进行自动类型转换等操作。
内联以代码膨胀(拷贝)为代价,仅仅省区了函数调用的开销,从而提高程序的执行效率。(开销指的是参数的压栈、跳转、退栈和返回操作)。
- 如果函数体内代码比较长,使用内联将导致可执行代码膨胀过大。
- 如果函数体内出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间将比函数调用的开销大得多。