目录
摘要
本文介绍C++中的inline,从inline的概念,到为什么设计者要用inline替换宏,再到使用inline的注意事项...
一:inline概念
被inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
未被inline修饰的Add函数汇编如下:

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

解释:二图一对比,就能看出未被inline修饰的Add函数,是会去call函数,也就是从函数地址去找到函数调用,所以必然会有栈帧;而被inline修饰后的内联函数Add,直接就地展开指向函数代码,不会展示栈帧,所以提高了效率!
注意:vs下想要通过汇编代码观察inline内联函数,需要进行适当的设置

二: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) { ... } 时:
-
a.c和b.c依然各自编译,a.o和b.o里也依然都有一份add函数的代码。 -
区别来了:这两份
add函数,都被标记为弱符号。 -
链接器对弱符号很宽容:它的规则是,“允许多个同名的弱符号存在。随便挑一个就行,其他的直接丢弃。”
所以,链接器看到 a.o 和 b.o 里都有 add 这个弱符号时,它会非常佛系地随便保留一份(比如选 a.o 里的),然后把其他所有重复的(b.o 里的)都扔掉,最终程序里只留一份。这就完美避开了“多重定义”的错误。
两者区别
| 类型 | 符号特性 | 链接器规则 | 能否定义在 .h 中? |
|---|---|---|---|
| 普通函数 | 强符号 | 全局唯一,出现多个就报 multiple definition 错误 | 绝对不行 |
inline 函数 | 弱符号 | 允许多个副本,链接器只保留一个,其余的丢弃 | 完全可以,也必须这样做 |
所以,“普通函数定义不能放 .h 文件”完全正确。而 C++ 给 inline 函数开的“后门”,就是允许它成为弱符号,从而可以安全地被放进头文件里,既实现了内联展开的效率,又遵守了单一定义规则。
📌 [ 作者 ] shylyly
📃 [ 首次发布 ] 2026.7.15
❌ [ 最新修改 ] 暂无
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教

5178

被折叠的 条评论
为什么被折叠?



