C++中的inline

目录

摘要

一:inline概念

二:inline替换宏的意义

三:使用inline的注意事项

1:inline是一种请求,可能被忽略

2:inline声明定义必须在同一文件

3:.h中不能存放普通函数的定义


摘要

本文介绍C++中的inline,从inline的概念,到为什么设计者要用inline替换宏,再到使用inline的注意事项...


 

一:inline概念

被inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。

未被inline修饰的Add函数汇编如下:

被inline修饰的Add函数汇编如下:

解释:二图一对比,就能看出未被inline修饰的Add函数,是会去call函数,也就是从函数地址去找到函数调用,所以必然会有栈帧;而被inline修饰后的内联函数Add,直接就地展开指向函数代码,不会展示栈帧,所以提高了效率!

注意:vs下想要通过汇编代码观察inline内联函数,需要进行适当的设置

1. release 模式下,查看编译器生成的汇编代码中是否存在 call Add
2. debug 模式下,需要对编译器进行设置,否则不会展开 ( 因为 debug 模式下,编译器默认不
会对代码进行优化,以下给出 vs2013 的设置方式 )

二:inline替换宏的意义

❓️:看到这里,发现inline其实不就是极其类似宏吗?我们用宏定义一个函数,那该宏也是在调用的地方就地替换,不存在栈帧啊,也能提升程序运行的效率啊?为什么C++非要搞个inline来替换宏呢?

💡:因为宏很容易出错!就拿上面这个简单的Add函数来说,你用宏定义的话,也容易出错:

#define ADD(a, b) a + b //❌️错误
#define ADD(a, b) (a + b)//❌️错误
#define ADD(a, b) ((a) + (b))//✅️正确

而这只是一个简单的Add函数,换成逻辑复杂一点的函数,更加容易出错!

所以C++祖师爷,发明了inline来替换宏,所以inline结合了宏的优点,同时摒弃了宏的缺点;宏的优点就是预处理的时候就替换展开了,不会产生栈帧。而inline有此优点,且只需在函数前加上inline关键字即可,也避免了宏定义容易出错的缺点~


 

三:使用inline的注意事项

1:inline是一种请求,可能被忽略

我们以前在C语言里用宏来定义短小的函数,目的很明确——在调用处就地展开,省去函数调用的栈帧开销,以此提升程序运行效率。

但宏是纯粹的文本替换,调用一次就生成一份代码,如果在main里调用10次,最终就会复制出10份完全相同的代码,造成代码膨胀。因此我们从来不会用宏去定义递归函数或长函数,因为递归无法在编译期完全展开,而长函数复制多份会严重增大可执行文件体积,反而可能因指令缓存被挤爆而拖慢程序。对这类函数,我们只会选择定义成普通函数,让所有调用点共享同一份代码,这才是合理的做法。

而祖师爷发明inline的时候,也考虑到的这点,所以当我们给一个递归函数或者长函数加上inline修饰的时候,编译器不会将其变成内联函数,而是忽略用户添加的inline修饰!编译器自我判断是为了减低程序员出错的概率!

所以,你甚至可以放心使用inline,因为编译器会对不合理的请求选择忽略,保留合理请求!

2:inline声明定义必须在同一文件

此知识点涉及到文件编译链接的过程,博客:编译链接的过程

我们知道普通的函数是可以声明在add.h文件,定义在add.cpp文件的,在我们使用函数的文件,比如main函数所处的test.cpp中,只需包含.h文件就可以使用该函数了

test.cpp文件因为包含了add.h文件,所以有了函数的声明,这样我们使用int result = add(1, 2); 的时候,编译器会检查我们的声明int add(int a, int b); 发现函数的使用是正确的,从而检查通过,但是此时并不知道函数的定义,也就是函数的地址是什么

等到链接的时候,此时每个文件都会有一个符号表,所以add.cpp中也会有符号表,此时链接器就会找到add.cpp中的add函数的定义,也就是找到了add函数的地址,所以链接器就把add函数的地址,给到了test.cpp需要的地方,回应了test.cpp的call指令

例子:

想象你在写一本书:

  • 头文件(声明) 就像书前的目录。目录会列出“第六章:函数指南”,并告诉你在第200页。

  • .cpp 文件(定义) 就是那一章的具体内容,的确放在第200页。

  • 编译过程:你可以在写第一章时直接引用“如第六章所述”,你不需要知道第六章的具体内容,只要目录的“承诺”就够了。

  • 链接过程:全书排版时,编辑才根据目录,把第一章的“如第六章所述”和第六章的实际内容关联起来。

你的程序能跑,是因为编译器相信了目录的承诺,而链接器最终兑现了这个承诺。

总结:你的 .test 文件没有包含 .cpp 中的定义,它只是通过包含头文件拿到了“承诺”,最后是靠链接器把分散在 add.cpp 里的“实现”给找补回来的。

inline 函数如果要在多个 .cpp 文件中使用,它的定义必须出现在头文件里,不能像普通函数那样“声明在头文件,定义在某个 .cpp 文件里”。

因为编译器在test.cpp中看到inline这个关键字,就知道这个函数是内联函数,直到这个函数是会就地展开的,所以编译器压根不会让采取链接器去找函数地址这一步骤,而是认为test.cpp中有内联函数的定义,而如果你的内联函数的声明和定义写在一个文件中,那最好不过;若你内联函数的声明和定义分离,则一定出错,因为使用内联函数的地方无法去展开实际内容!

所以,“声明和定义分离”对于 inline 函数来说是行不通的,定义必须对调用点可见。

若你强行让inline修饰的内联函数声明和定义分离,则会触发"符号多重定义"的报错,其实就是链接式报错,链接的时候出错了

✅️正确做法:把inline函数的声明和定义都放在.h文件中


 

3:.h中不能存放普通函数的定义

.h中不能存放普通函数的定义,其实就是指不能把一个普通函数的声明定义都放在.h文件中!

假设.h文件里若存放了Add函数定义,当多个.c文件包含这个.h时,每个.c文件编译出来的目标文件(.o)中,都会产生一个Add对应的强符号。链接器在合并各个目标文件时,不允许同名的强符号存在多个实例,这违反了‘单一定义规则’,所以链接器会报‘多重定义’错误。

❓️:如果已经把普通函数的声明定义都放在了.h中,怎么解决呢?

💡:很简单,解决方法就是在这个普通函数Add前加上static,形成内部链接属,编译器认为这个Add函数是仅本文件可见的

a.c 包含它 → a.o 里产生了一个叫 f 的符号,被标记为本地/内部
b.c 包含它 → b.o 里产生了一个叫 f 的符号,同样被标记为本地/内部
当链接器处理 .o 文件时,它的工作规则是:
对于“全局符号”:必须唯一,发现重复就报错。
对于“本地符号”:链接器直接忽略它们,不参与全局符号表的合并!

所以就避免了‘多重定义’错误。

但是一个优秀的编码习惯,是不会让任何普通函数的定义和声明同时存在于.h文件中的, 都是.h文件放声明,.cpp文件放定义,这才合规合理,而不是为自己不良编码习惯采取补救措施!

❓️:那为什么inline函数可以声明定义都放在.h文件中,不引起‘多重定义’错误?

💡:这就是 inline 关键字起的“魔法”作用。它把函数的身份从“强符号”变成了弱符号

当你在头文件里写 inline int add(int a, int b) { ... } 时:

  1. a.c 和 b.c 依然各自编译,a.o 和 b.o 里也依然都有一份 add 函数的代码。

  2. 区别来了:这两份 add 函数,都被标记为弱符号

  3. 链接器对弱符号很宽容:它的规则是,“允许多个同名的弱符号存在。随便挑一个就行,其他的直接丢弃。”

所以,链接器看到 a.o 和 b.o 里都有 add 这个弱符号时,它会非常佛系地随便保留一份(比如选 a.o 里的),然后把其他所有重复的(b.o 里的)都扔掉,最终程序里只留一份。这就完美避开了“多重定义”的错误。

两者区别

类型符号特性链接器规则能否定义在 .h 中?
普通函数强符号全局唯一,出现多个就报 multiple definition 错误绝对不行
inline 函数弱符号允许多个副本,链接器只保留一个,其余的丢弃完全可以,也必须这样做

所以,“普通函数定义不能放 .h 文件”完全正确。而 C++ 给 inline 函数开的“后门”,就是允许它成为弱符号,从而可以安全地被放进头文件里,既实现了内联展开的效率,又遵守了单一定义规则。

📌 [ 作者 ]   shylyly
📃 [ 首次发布 ]   2026.7.15
❌ [ 最新修改 ]   暂无
📜 [ 声明 ]   由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值