inline 对于函数内联优化有效吗?
现代 C++ 中
inline对于指示函数内联优化,有效吗?
It depends!
简短地说,这取决于编译器。
对于 GCC,一定程度上是有效的;而对于 Clang,基本上是无效的。
正文
很多 OIer 喜欢写 inline,但是也有人从来不写(反正我还在打 OI 的时候就没写过)
反对写 inline 的人的观点如下:
- 在 C++11 及以后的标准中,
inline的含义是「允许重复定义」而不是「指示编译器进行内联优化」,所以写inline完全无效 - 递归函数没法内联(因为递归的层数是在运行时确定的)
- 编译器比你更清楚哪些函数应当被内联而哪些不应当
习惯写inline可能会导致写出inline func(...)这类 Dev-C++ 可以编译过但是在 Linux 上编译不过的东西
我最初是赞成这些观点的。但是后来实测发现 GCC 加 inline 后生成的汇编代码明显比没有 inline 的长(甚至是递归函数!),我开始怀疑这个观点。但是遗憾的是,我不懂汇编,所以没法确定
最近在看现代 C++ 的书,忽然又想起了这个问题,于是编写了测试代码,开始实验~
虽然说,我依然不懂汇编,但是,现在有 LLM(
1 | // test.cpp |
编译命令:
1 | g++ test.cpp -S -O2 -std=c++17 -o no_inline_gcc.s |
我目前用的编译器都是比较新的版本,所以我选择了较新且较为稳定的 C++17 标准,并且打开了 O2
汇编输出:
先看一下生成代码的行数
1 | $ wc no_inline_gcc.s |
很奇怪,对于 clang,开 inline 代码居然变短了
粗略地看了一下汇编,似乎没有 inline 的情况下两个编译器对三个函数都有定义;clang 输出的代码中 main 函数都很大(而且开不开 inline 似乎没有什么区别),而 GCC 在开 inline 后 main 函数体积增大的同时 recursive_func 的体积也增长了许多
我使用 diff 进行了比较,发现 clang 是否开 inline 生成的代码几乎没有区别,而减少的行数是前两个函数的定义。在没有 inline 的情况下,这些定义的函数似乎也没有被使用,应该是为后续可能的链接进行了预留
最后,我找 LLM 分析了这些汇编代码,结果是这样的(✅ = 内联,❎ = 不内联)
| 函数 | GCC,无 inline |
GCC,inline |
Clang,无 inline |
Clang,inline |
|---|---|---|---|---|
small_func |
✅ | ✅ | ✅ | ✅ |
loop_func |
❎ | ✅ | ✅ | ✅ |
recursive_func |
❎ | ✅ 递归展开 | ❎ | ❎ |
这恰好印证了我在汇编代码中的发现
此外,「递归函数没法内联」这类说法是错的
结论
这样,结论就已经很明确了:
- 对于小函数,不论是 GCC 还是 Clang,编译器都会主动内联
- 对于 Clang,指定
inline在函数内联优化上完全没有作用,编译器会自行决定是否优化 - 对于 GCC,指定
inline对于函数内联优化有明显作用,甚至会让编译器进行递归展开 - 对于所有编译器,
inlineC++ 标准指定的「运行重复定义」的语义都会保证
总体上来看,对于 Clang,inline 的语义与现代 C++ 标准规定的一致;而对于 GCC,inline 的语义更像是在传统 C++ 中的语义,即指示内联优化
至于为什么会是这样,我猜测这与 GCC 和 Clang 的历史有关。Clang 诞生于 2007 年,它作为 C++ 编译器成熟时正是 C++11 的年代,所以其设计上就是一个完全的现代 C++ 编译器;而 GCC 诞生时连 C++98 都没有,考虑到它继承了相当多的 90 年代的代码,在表现上更像是传统的 C++ 编译器也是完全合理的
那么,究竟写不写 inline 呢?
可写可不写。
不过,严谨地说,对于 OIer(编译器只有 GCC),卡常的时候或许写上比较好?(不过函数调用的这一点开销恐怕对于整体是微乎其微吧,况且即便是内联了,也不一定真的会提升性能)
至于工程上,如果使用 GCC 的话,或许应当按照 C++ 之父 Bjarne Stroustrup 的建议,测试之后再决定?
反正我不写,因为懒
吐槽:千问分析汇编代码完全在胡说八道,相比之下 Gemini 好很多
为什么御三家编译器中缺 MSVC,因为我这里没有