☰
C++中的inline
2026/10/2 8:14:07 网站建设 项目流程

目录

摘要

一: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函数是可以声明在add.h文件,定义在add.cpp文件的,在我们使用函数的文件中,比如main函数对应的test.cpp中,只需包含.h文件就可以使用该函数了

main所在的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文件没有包含.cpp中的定义,它只是通过包含头文件拿到了“承诺”,最后是靠链接器把分散在add.cpp里的“实现”给找补回来的。

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

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

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

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

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


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

我们在初次学习C语言时,常常把函数的声明定义都放在一个.c文件中,后面为了高效管理各种文件,所以我们才将函数的声明放在.h,定义放在.c中,但是我们从未思考过,如果把函数的声明定义都放在.h中会怎么样?

结论:.h中不能存放普通函数的定义,不能把一个普通函数的声明定义都放在.h文件中!

假设.h文件里若存放了Add函数定义,当多个.c文件包含这个.h时,每个.c文件编译产生的目标文件.obj中的符号表都会产生一个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
❌ [ 最新修改 ] 2026.10.1
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询