目录摘要一inline概念二inline替换宏的意义三使用inline的注意事项1inline是一种请求可能被忽略2inline声明定义必须在同一文件3.h中不能存放普通函数的定义摘要本文介绍C中的inline从inline的概念到为什么设计者要用inline替换宏再到使用inline的注意事项...一inline概念被inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的代码处就地展开没有函数调用建立栈帧的开销内联函数提升了程序运行的效率。未被inline修饰的Add函数汇编如下被inline修饰的Add函数汇编如下解释二图一对比就能看出未被inline修饰的Add函数是会去call函数也就是从函数地址去找到函数调用所以必然会有栈帧而被inline修饰后的内联函数Add直接就地展开函数对应的代码不会展示栈帧所以提高了效率注意vs下想要通过汇编代码观察inline内联函数需要进行适当的设置1.在release模式下查看编译器生成的汇编代码中是否存在call Add2.在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的注意事项1inline是一种请求可能被忽略我们以前在C语言里用宏来定义短小的函数目的很明确——在调用处就地展开省去函数调用的栈帧开销以此提升程序运行效率。但宏是纯粹的文本替换调用一次就生成一份代码如果在main里调用10次最终就会复制出10份完全相同的代码造成代码膨胀。因此我们从来不会用宏去定义递归函数或长函数因为递归无法在编译期完全展开而长函数复制多份会严重增大可执行文件体积反而可能因指令缓存被挤爆而拖慢程序。对这类函数我们只会选择定义成普通函数让所有调用点共享同一份代码这才是合理的做法。而祖师爷发明inline的时候也考虑到的这点所以当我们给一个递归函数或者长函数加上inline修饰的时候编译器不会将其变成内联函数而是忽略用户添加的inline修饰编译器自我判断是为了减低程序员出错的概率所以你甚至可以放心使用inline因为编译器会对不合理的请求选择忽略保留合理请求2inline声明定义必须在同一文件此知识点涉及到文件编译链接的过程博客编译链接的过程我们知道普通的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) { ... }时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❌ [ 最新修改 ] 2026.10.1 [ 声明 ] 由于笔者水平有限文中难免有疏漏或不妥之处还望读者不吝赐教