c++之inline关键字从基础到通天(一篇即毕业系列)

c++之inline关键字从基础到通天(一篇即毕业系列)

文章目录

欢迎收看一篇即毕业系列,本系列的其它内容如同本篇一样优秀喔!!
那么话不多说!

关于 inline,我们直接了解以下几个知识点即可。

inline 是一个请求(而非命令)

inline 关键字用于向编译器发出一个请求,建议将函数体在每个调用点内联展开。这意味着编译器在编译过程中,可能会将函数的代码直接插入到调用该函数的地方,而不是通过通常的函数调用机制来执行。

需要注意的是,inline 只是一个建议,编译器可以选择是否接受这个建议。编译器可能会基于多种因素(如函数的大小、复杂性、调用频率以及整体代码的优化目标)来决定是否进行内联展开。

inline 函数通常用于小函数

inline 函数通常用于那些执行速度快且调用频繁的小函数。这些函数通常只有几行代码,并且不包含复杂的控制结构或大量的计算。

通过将这些小函数内联展开,可以减少函数调用的开销(如栈帧的创建和销毁、参数传递等),从而提高程序的执行效率。

对于较大的函数或包含复杂逻辑的函数,内联展开可能会导致代码膨胀,甚至可能降低性能。

inline 函数的定义通常放在头文件中

由于 inline 函数需要在每个调用点展开,因此其定义需要在编译时对每个编译单元可见。这通常意味着 inline 函数的定义应该放在头文件中,而不是源文件(.c)中。这样做可以确保在链接时不会出现重复定义的问题,因为每个编译单元都会包含 inline 函数的定义,并且编译器会处理这些重复的定义(实际上,由于 inline 的特性,编译器会将其视为建议而非强制要求,因此不会有链接时的符号冲突)。

如果 inline 函数在多个编译单元中被引用,并且没有通过 static 关键字限制其链接性,那么在某些编译器和链接器实现中可能会遇到链接错误。为了避免这种情况,通常建议将 inline 函数声明为 static,从而限制其在单个编译单元内的可见性。

推荐阅读:https://stackoverflow.com/questions/31108159/what-is-the-use-of-the-inline-keyword-in-c

inline 函数不能包含复杂的控制结构

inline 函数通常应该避免包含复杂的控制结构,如循环和递归。这是因为内联展开这些结构可能会导致代码膨胀,增加程序的内存占用,并且可能不会带来性能上的提升。

特别是递归函数,由于递归调用本身的开销和栈空间的使用,内联展开通常是不合适的。

对于包含复杂控制结构的函数,即使它们很小,编译器也可能选择不进行内联展开。

编译器可能忽略 inline 请求

编译器可能会忽略 inline 请求,特别是在以下情况下:

  • 函数体较大:如果函数体包含大量代码,编译器可能会认为内联展开会导致代码膨胀,从而选择不进行内联。
  • 包含复杂逻辑:如果函数包含复杂的控制结构、大量的计算或条件分支,编译器可能会认为内联展开不会带来性能上的优势,甚至可能降低性能。
  • 优化级别:编译器的优化级别也会影响其是否接受 inline 请求。在较低的优化级别下,编译器可能会更加保守地选择不进行内联展开。
  • 链接时优化:在某些情况下,即使函数在源代码中被标记为 inline,编译器也可能在链接时决定不进行内联展开。这取决于编译器的链接时优化能力和策略。

验证是否 inline

代码块

#include<stdio.h>#include<time.h>#include<stdint.h>// 定义一个inline函数inlineintadd_inline(int a,int b){return a + b;}// 定义一个普通函数intadd_normal(int a,int b){return a + b;}intmain(){constdouble N =10000000000000000;// 函数调用次数 clock_t start, end;double cpu_time_used;// 测试普通函数 start =clock();for(double i =0; i < N; i +=0.0000000001){add_normal(i, i);} end =clock(); cpu_time_used =((double)(end - start))/ CLOCKS_PER_SEC;printf("Normal function took %f seconds to execute \n", cpu_time_used);// 测试inline函数 start =clock();for(double i =0; i < N; i +=0.0000000001){add_inline(i, i);} end =clock(); cpu_time_used =((double)(end - start))/ CLOCKS_PER_SEC;printf("Inline function took %f seconds to execute \n", cpu_time_used);return0;}

汇编代码分析

add_normal(int, int): lea eax,[rdi+rsi*1] ret data16 data16 cs nop WORD PTR [rax+rax*1+0x0] main: push rbx call 1040 <clock@plt> mov rbx,rax call 1040 <clock@plt> sub rax,rbx cvtsi2sd xmm0,rax divsd xmm0,QWORD PTR [rip+0xe8a] # 2008 <_IO_stdin_used+0x8> lea rdi,[rip+0xe8b] # 2010 <_IO_stdin_used+0x10> mov al,0x1 call 1030 <printf@plt> call 1040 <clock@plt> mov rbx,rax call 1040 <clock@plt> sub rax,rbx xorps xmm0,xmm0 cvtsi2sd xmm0,rax divsd xmm0,QWORD PTR [rip+0xe5c] # 2008 <_IO_stdin_used+0x8> lea rdi,[rip+0xe8a] # 203d <_IO_stdin_used+0x3d> mov al,0x1 call 1030 <printf@plt> xor eax,eax pop rbx ret 

代码中有 add_normal 的标签,却没有 add_line 的标签。我们可以通过汇编代码查看是否被 inline,汇编代码中,被内联的函数不会有函数的标签,普通的函数会有函数标签。具体详见:https://godbolt.org/z/jjGrjW85q(注意,要使用-O2编译)。

其他验证方法

也可以通过gcc编译器,搭配-fopt-info参数,输出编译器的所有优化信息(https://godbolt.org/z/4Ycx35hTh),如图:

Output of x86-64 gcc 15.1 (Compiler #1) <source>:32:19: optimized: Inlining int add_inline(int, int)/13 into int main()/15. <source>:23:19: optimized: Inlining int add_normal(int, int)/14 into int main()/15. ASM generation compiler returned: 0 <source>:32:19: optimized: Inlining int add_inline(int, int)/13 into int main()/15. <source>:23:19: optimized: Inlining int add_normal(int, int)/14 into int main()/15. Execution build compiler returned: 0 Program returned: 0 Normal function took 0.000001 seconds to execute Inline function took 0.000001 seconds to execute 

推荐阅读

Read more

【算法】连通块问题(C/C++)

【算法】连通块问题(C/C++)

目录 连通块问题 解决思路 步骤: 初始化: DFS函数: 复杂度分析  代码实现(C++) 题目链接:2060. 奶牛选美 - AcWing题库 解题思路: AC代码:  题目链接:687. 扫雷 - AcWing题库  解题思路: AC代码: 总结: 连通块问题 连通块问题(Connected Component Problem)是一个经典的图论问题,通常用来找出图中的所有连通分量。给定一个无向图,连通块问题的目标是确定图中有多少个连通分量(即有多少个互相连通的节点组成的集合) 解决思路 1. 深度优先搜索(DFS) 或 广度优先搜索(BFS): * 可以从任意未访问的节点出发,进行DFS或BFS,标记所有能够访问到的节点,代表这个连通分量。 * 重复这个过程,直到所有节点都被访问为止。每次从新的未访问节点出发时,就代表发现了一个新的连通分量。 2.

By Ne0inhk
【C++:继承】C++面向对象继承全面解析:派生类构造、多继承、菱形虚拟继承与设计模式实践

【C++:继承】C++面向对象继承全面解析:派生类构造、多继承、菱形虚拟继承与设计模式实践

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的C++专栏简介: 目录 C++的两个参考文档 4  ~>  派生类的默认成员函数专题 4.4  实现一个不可继承类实现 4.4.1  间接实现:【C++98】构造函数私有的类不能被继承 4.4.2  直接实现:final关键字修改基类 4.4.3  代码实现 4.4.4  final关键字

By Ne0inhk
从二叉树到 STL:揭开 set 容器的本质与用法

从二叉树到 STL:揭开 set 容器的本质与用法

前言:         上次介绍完二叉搜索树后,更新中断了一段时间,先向大家致歉。最近学习状态有些起伏,但我正在努力调整,相信很快会恢复节奏。今天我们继续深入探讨——关联容器,它在算法和工程中都非常常见和重要。 1.之前的遗漏         我之前写的二叉搜索树其实没有写完,我仅仅写了一个节点只能存储一个值的二叉搜索树。在我们日常的工作中,这种树的使用率其实还是比较低的。最受欢迎的是里面存储两个值的二叉搜索树,这个可以类比Python中的字典。相信学过python的读者对此不陌生,字典其实存放了一对值,分别是Key和Value,类比英文字典中的英语和汉字,我们通过英文(Key)来找到对应的中文(Value)。这其实才是我们常使用到的二叉搜索树,下面我通过举例来帮助各位理解这两棵树的区别。 1.1.Key搜索场景         举个例子,现在很多小区配有地下停车库。业主的车牌号会录入系统中,车辆进入时由系统识别并判断是否允许进入。这就是典型的 Key 搜索场景 —— 只需根据一个关键字(车牌号)进行查找。 1.2.Key/Value搜索场景         还是以我们

By Ne0inhk
C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板 💡 学习目标:掌握模板进阶技术的核心用法,理解模板特化的深层应用、类型萃取的实现原理,以及可变参数模板的灵活使用,提升泛型编程的实战能力。 💡 学习重点:模板特化的进阶场景、类型萃取工具的设计与应用、可变参数模板的展开技巧、折叠表达式的使用方法。 一、模板特化进阶:处理复杂类型场景 💡 模板特化不只是针对单一类型的定制,还能处理指针、引用、数组等复杂类型,实现更精细的类型适配逻辑。 1.1 指针类型的模板特化 通用模板默认处理普通类型,我们可以为指针类型单独编写特化版本,实现指针专属的逻辑。 #include<iostream>#include<string>usingnamespace std;// 通用模板:处理普通类型template<typenameT>classTypeProcessor{public:staticvoidprocess(T data){ cout

By Ne0inhk