目录
一、内联函数是什么
在学习内联函数之前,我们先引入C的一个概念:宏
用宏实现a+b的操作
#define Add(a,b) a + b X
#define Add(a,b) ( a + b ) X
#define Add(a,b) (a) + (b) X
#define Add(a,b) ( (a) + (b) ) ; X
#define Add(a,b) ( (a) + (b) ) √
这里只解释为什么( a + b )是错的。
例:Add ( 1 | 3 , 2 & 5)
这里会改变运算顺序,本来先对两个位分别运算,结果却变成了先算 | ,然后算 + ,最后算 &
这就是C语言宏的缺陷之一
1、容易出错,语法细节多
2、不能调试
3、没有类型检查(如上式的 a 和 b 甚至可以是 char 类型)
综上,C++祖师爷决定做一个“违背祖宗”的决定:抛弃C的宏(但你依旧可以用,反正我是不想再用了),采用内联函数(linine)来代替宏
二、内联函数怎么写
我们完全可以像写函数一样写内联函数,只需要在前面加上 inline 就行
inline int Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int a = 1;
int b = 2;
int c=Add(a,b);
}
上图就是一个内联函数,或者你就可以叫他宏(不过还是不建议这么叫,毕竟还是很不一样的)
看样貌无非就是常规函数的定义前面加上一个inline罢了
如果光看,其实平平无奇。他不就和普通的函数差不多嘛?那为啥还要设计一个内联函数徒增烦恼呢?
三、内联函数有啥用
光看肯定是看不来个123的,我们直接调试来观察
!在调试之前,先调一个设置,不然是看不出来的!
调试状态下右键代码区,选择反汇编
调试结果如下:
你会发现:明明函数内容一模一样,但是汇编的指令却不一样,why?
这就是内联函数的独到之处!
我们先理解 call指令 是什么意思:
说简单点,call指令就是调用一个函数。先call他的地址,然后找过去,执行函数内容
那么就意味着,inline并没有调用函数,所以这就有了内联函数的特点:
内联函数是一种以空间换时间的做法,在编译阶段,会用函数体替换函数的调用,他减少了函数创建栈帧的时间开销(因为没有了函数调用,就不用开辟栈帧)
四、注意事项
1、内联没展开
有时候你会发现他并没有按你想要的方式内联展开
不同的编译器对inline的实现机制可能不一样
你提出的inline对于编译器来说只是一个请求,而编译器可以拒绝你的请求!
因为程序员错误的使用内联函数,会浪费存储空间资源,所以编译器认为不如自己管理这个内联函数。如果你想要让他成为内联,编译器认可后就可以通过你的建议
2、 内联函数不一定比一般函数好用
注意:内联函数是一种以空间换时间的做法,他虽然节省了开辟栈帧的时间,但是他的本质是用函数体进行替换(与宏一模一样,只是开始替换的阶段不一样。宏是在预处理阶段替换的,而内联是在编译阶段替换的)
如图,对于常规函数来说,每次执行函数时,调用的函数都在同一块空间
代表:每次调用的时候就创建栈帧,返回的时候就销毁栈帧。所以不管调用add函数多少次,实际占用的空间也就是一个add函数的栈帧
但是,如果换做内联函数
如图,内联函数中不存在函数调用(没有call),他是把函数体替换进去(不是直接copy内容,而是会有一定的调整)
设想一下,如果这个add函数里面有100行代码,main函数里面调用1000次
那么main函数会变成什么样呢?
1、用非内联函数
每次调用占一行,add函数占100行,共记100+1000==1100行
2、用内联函数
每个调用都是一次替换,一次替换要放入100行内容,总共替换1000次,共计100*1000=100000行
综上:简直是指数级提升!!内联函数掉大分
3、内联函数的声明与定义
1、声明与定义不分文件写(要么在本文件下,要么在包含的头文件下)
2、声明与定义分文件写
可以把内联函数就理解成一个宏,碰到add就替换掉他
因为内联函数是直接替换函数的调用、链接的时候不可能生成地址,所以如果定义在第3个文件中,那么这个函数是没有地址的,链接的目的就是找函数地址,这样就发生了:只有add函数的声明,而没有定义,所以就会报错
五、内联函数在什么阶段展开
结论:在编译阶段展开
这里用Linux下的gcc编译器演示(由于作者目前还没详细了解汇编语言,所以暂时用排除法跟大家演示)
1、先创建.cpp文件
2、对.cpp进行预处理,生成.i文件
3、再对其进行编译,生成.s文件
在这个文件中内联就展开了(作者通过排除法证明),因为:
再往后是汇编阶段(将汇编指令翻译成二进制代码,所以这一阶段只是存粹的翻译)
再然后是链接阶段(这一阶段可以理解为只是将不同函数的地址信息公开,让函数调用时能找到函数的定义。而前面我们以已经证明,内联函数没有调用,也就不会生成地址,所以他没有参与这个链接环节)
六、什么时候会用到内联函数
由于他的特性:
1、是由宏改进而来
2、可以减少栈帧开辟的时间
3、便于调试
4、展开会消耗空间
所以我们一般在优化规模较小、流程直接、频繁调用的时候可以想想内联函数。还有,尽量别递归使用内联函数,很多编译器不支持,而且行数过多的也不好内联展开