被知乎大佬嘲讽后的一个月,我重新研究了一下内联函数

本文作者分享了在被知乎大佬指出内联函数理解错误后,重新深入研究内联函数的心路历程。从菜鸟阶段的初步认识,到初识阶段理解函数调用开销,再到进阶阶段对编译器优化的深入探讨,作者逐步揭示了内联函数的真正意义。文章介绍了内联函数的优点、缺点、以及编译器如何在不同情况下决定是否内联,同时也讨论了链接时优化(LTO)和过程间优化(IPO)等概念,强调了编译器在优化中的重要作用。
摘要由CSDN通过智能技术生成

这绝不仅仅是一篇讲内联意义的文章,参考我的学习过程,可能对你的知识整合有很大帮助

之前写了一篇总结c++面试的文章,被大佬纠出来很多关于内联的问题与错误。抱着不误导别人以及学习的态度,我在之后的一个月里抽时间重新研究了一下内联函数,确实学到了很多以前不了解的知识。学习么~就是一个不断打破之前认知并重构知识的过程,每个人都是从一个什么都不懂的菜鸟逐渐成长为一个大牛的。
在这篇文章里,我会由浅入深的分析不同阶段的我对内联函数的认识,重构我的知识体系。即使你之前对inline不了解,也可以看得懂这篇文章。 文中会有很多引用的参考链接,我会统一放到文末的位置。

菜鸟阶段(初步学习计算机组成原理,C++语法,C语言语法,汇编语言等):

上大学第一次接触C++,然后了解到了内联函数。啥是内联函数?简单理解就是编译时把函数的定义替换到调用的位置。

inline int Add(int a, int b)
{
   return a + b;
}
int main()
{
   int num1 = 1;
   int num2 = 2;
   int myNum = Add(num1, num2);
}
//这样的代码内联之后大概就是
int main()
{
   int num1 = 1;
   int num2 = 2;
   int myNum = num1 + num2;
}

好的,感觉好像还挺简单的。啥?你问我啥是编译?嗯。。编译就是把你的代码通过编译器分析一下然后转换成计算机能直接读懂的语言(汇编),最后生成一个可执行的程序(或可被调用的库)。
当然,我这么解释有点不太权威,咱们再看看维基百科关于内联函数的定义

在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。【参考:内联函数维基百科

那么内联函数有什么有点呢?当然是减少函数调用带来的开销了,几乎每本C++入门书籍、百科以及博客都是这么说的。不过,什么是函数调用开销?额,反正调用函数肯定要消耗CPU运算吧,肯定也有内存参与,肯定有开销,嗯。
另外,我还从书上了解一些相关的知识,如直接在类的头文件里面定义的函数都是自动内联的(并不对),内联相比宏定义有类型检查、可支持类的访问控制等优点。

这时候我知道的专业名词有:汇编、编译、内联、CPU、函数调用、内存地址,但是他们之间的关系几乎是一头雾水了。
这个阶段的知识图谱大概是:
在这里插入图片描述

初识阶段(学习了一些编译原理知识,稍微深入的了解了一些C++特性,有一些相关编码经验):

之前总是说减少函数调用开销,那么这个调用开销到底是指什么?这时候的我发现有一些面试里面会问到这个问题,所以还真有必要理解一下了。

我们常说,C语言程序内存分为常量区、代码区、静态全局区、栈区、堆区。当我们的程序运行时,我们的编译后的二进制程序(这个二进制程序的分布格式差不多就是前面说的那几个区,里面会有各种汇编命令,可参考书籍《Windows核心编程》)就会被放到操作系统的内存里面,函数代码段被放在所谓的代码区,局部变量与函数参数被放在栈区。函数调用就发生在栈区里面,每次调用的时候会把当前函数的相关内容压入到栈里面处理寄存器相关的数据信息(所谓没有地址的右值很多情况就是指通过寄存器存储的数据)。然后,调用地址指向我们要执行的函数位置,开始处理函数内部的指令进行计算,当函数执行结束后,要弹出相关数据,处理栈内数据以及寄存器数据。【参考:浅谈C/C++堆栈指引——C/C++堆栈很强大
这个过程也就是所谓的“函数调用开销”。

函数调用栈
这里我们再总结一下消除函数调用的直接好处【参考:Inline expansion 】:
1.它消除了函数调用过程中所需的各种指令:包括在堆栈或寄存器中放置参数,调用函数指令,返回函数过程,获取返回值,从堆栈中删除参数并恢复寄存器等。
2.由于不需要寄存器来传递参数,因此减少了寄存器溢出的概率。当使用引用调用(或通过地址调用或通过共享调用)时,它消除了必须传递引用然后取消引用它们。

当然缺点我们也应该了解,使用不当的话就会造成代码膨胀(也就是生成的可执行程序会变大),影响cache对数据的命中,如果你设计了一个函数库,调用你的内联函数还会造成客户代码的重新编译。一般高速缓存里面会分为指令缓存(instruction cache)以及数据缓存(data cache),inline的使用不当对二者都可能造成影响。首先,过多的内联代码会使原来本可以存储到ICache的指令分散,导致指令缓存的命中降低,从内存取数据会严重影响效率。其次,inline会导致代码膨胀,增加可执行程序(动态库、静态库)体积,造成额外的换页行为,进而可能会导致数据缓存的命中率降低。
上面说的缺点还比较抽象,很多情况好像都可以接受。而还有一些特定情况,内联将会造成很严重的后果,如递归函数的内联可能造成代码的无限inline循环。所以编译器在这些特殊情况下会拒绝内联,常见的包括虚调用,函数体积过大,有递归,可变数目参数,通过函数指针调用

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值