inline 函数
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 15 篇,控制流与函数子族的收尾。inline是给编译器的"建议":把这个小函数展开到调用点,省掉函数调用开销。它和 C 的宏是同一目的的两条路,但宏的坑(副作用、类型、括号)它全躲开了。
本文要回答的问题
inline到底干什么?为什么只是"建议"?- 它和 C 的宏是什么关系?
- 什么时候写 inline?什么时候写了也白写?
一、inline:把函数展开到调用点
函数调用有开销:压栈、跳转、返回。如果函数很小(比如 int max(int a, int b)),调用开销可能比函数体本身还大。inline 就是告诉编译器:"这函数别真的调用了,直接把它展开到调用处。"
inline int max(int a, int b) {
return a > b ? a : b;
}
int r = max(3, 5); // 可能被编译成 int r = (3 > 5 ? 3 : 5);编译器把 max(3, 5) 原地展开,省一次调用。注意"可能"——inline 是建议不是命令,编译器有完全的自由裁量权:函数太大、太复杂、有递归,它就不展开了。反过来,没写 inline 的小函数编译器在 -O2 下也经常自动内联。现代编译器面前,inline 更多是"请求",内不内联它说了算。
二、inline 和宏:同一目的,两条路
C 程序员处理"小函数调用开销"的老办法是宏:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int r = MAX(3, 5);宏在预处理期做纯文本替换,好处是零调用开销,坏处一堆:
#define SQUARE(x) ((x) * (x))
SQUARE(2 + 3) // 展开成 ((2 + 3) * (2 + 3)),正确(因为括号)
SQUARE(++x) // 展开成 ((++x) * (++x)),x 自增两次!副作用爆炸宏的经典死法:参数被求值多次。SQUARE(++x) 里 ++x 出现两次,行为完全失控。inline 函数没有这个问题——它是真正的函数,参数按规则求值一次:
inline int square(int x) { return x * x; }
square(++x); // 只自增一次,行为正常| 对比项 | 宏 | inline 函数 |
|---|---|---|
| 处理阶段 | 预处理(文本替换) | 编译期 |
| 类型检查 | 无 | 有(编译期检查) |
| 参数副作用 | 可能被求值多次 | 求值一次 |
| 调试 | 替换后难跟 | 正常函数可断点 |
| 作用域 | 全局文本 | 遵循函数/命名空间规则 |
一句话:宏是文本游戏,inline 是正经函数。现代 C++ 里能用 inline 就别用宏,宏留给条件编译(#ifdef)那种场景。
三、inline 的另一个身份:头文件里的"重复定义豁免"
这是 inline 最容易忽略的工程意义。普通函数定义放头文件,多个 .cpp 都 include 它,链接时会报"重复定义";inline 函数放头文件就没事——编译器知道多个翻译单元里的定义是同一个东西。
// util.h
inline int twice(int x) { return x * 2; } // 放头文件,OK
int not_inline(int x) { return x * 2; } // 放头文件:多个 cpp include 就重复定义所以 C++ 里"小工具函数放头文件"的习惯,靠的就是 inline。这也和 C 的处理方式不同——C 里放头文件的函数定义要用 static 或复杂的 inline 规则(C99),C++ 一条 inline 全搞定。
四、什么时候写 inline
| 场景 | 建议 |
|---|---|
| 一两行的小函数(getter、比较、简单运算) | 值得写 |
| 几十行的大函数 | 别写,编译器不会理的 |
| 递归函数 | 写不写都没用(递归无法内联到底) |
| 虚函数 | 动态分发下通常不内联 |
| 放在 .cpp 里的函数 | 写不写无所谓,编译器自己判断 |
入门阶段的实用建议:头文件里的小工具函数加 inline(顺便解决重复定义);大函数和递归别费这个心。性能上真正在意时,用 -O2 让编译器自己决定,再用 perf 实测,别靠猜。
五、与本站主线衔接
- 内联与调用开销的实测:
demos/cpu-demo的-O2优化演示就是"编译器替你干活的活例子",见demos; - 宏的展开与预处理机制,见 C 预处理、宏与调试;
- 调用约定、栈帧开销的寄存器级细节,见专家层调用约定与栈帧;
- 下一篇进入新子族:类与对象(类定义与成员),函数的各种姿势在类里会更丰富。
一句话总结
inline 是给编译器的"把函数展开到调用点"的建议(不是命令,编译器说了算),同时它还承担"头文件里的函数定义不重复定义"的工程职责;相比 C 宏,inline 是类型安全的真函数,参数只求值一次,没有副作用爆炸的坑;小函数值得写,大函数/递归写了也白写,性能最终靠 -O2 + 实测。
上一篇:值传递 vs 引用传递 下一篇:类定义与成员