社区讨论
inline 真的没用吗?
学术版参与者 102已保存回复 668
讨论操作
快速查看讨论及其快照的属性,并进行相关操作。
- 当前回复
- 631 条
- 当前快照
- 2 份
- 快照标识符
- @mify7g64
- 此快照首次捕获于
- 2025/11/26 19:55 3 个月前
- 此快照最后确认于
- 2026/02/11 02:33 上周
经过相关的查询、讨论和实验,我得出了以下结论。
inline 的双重语义
inline 函数具有以下的两种语义:- 允许函数在多个编译单元中重定义(不会导致链接错误)。
- 建议编译器进行内联优化。
实际上,C++ 标准已经不再承认“建议内联优化”的语义,并且实际上,编译器通常也不会听取这个建议。
尽管标准是这么说,但是很多情况下我们会发现给小函数加上
inline 关键字,仍然可以大幅提升调用速度。似乎标准说的话在“骗人”。其实并非如此,接下来我讲一下自己的理解。
-fPIC
根据相关公示,洛谷启用了一个名为
-fPIC 的编译选项。这个编译选项要求编译器生成“位置无关”的代码来提升安全性,同时使得外部链接的函数(普通函数)可以被运行时替换。这需要通过一种名为 PLT 的机制来支持。
具体来讲,需要先记录对应函数的真实地址,每一次函数调用都需要替换为先获取真实地址,再进行寻址和调用。这也同时让编译器无法进行内联优化。
例如以下代码:
CPPauto f(int x) -> int {
return x - 1;
}
auto g(int x) -> int {
return f(x) - 1;
}
在开启
ASM-fPIC 编译选项之后,编译结果如下:f(int):
lea eax, -1[rdi]
ret
g(int):
sub rsp, 8
call f(int)@PLT
add rsp, 8
sub eax, 1
ret
即使是这样简单的函数,也进行了一次函数调用。而没有这个编译选项,编译结果如下:
ASMf(int):
lea eax, [rdi-1]
ret
g(int):
lea eax, [rdi-2]
ret
这就很符合直觉了,所以我认为,
-fPIC 是导致编译器内联优化失效的“罪魁祸首”。这甚至还会影响到全局变量和数组的访问效率,因为多了一次间接寻址。解决方案
为了解决这个性能问题,主要的思路是避免全局函数、变量作为外部链接。有几种大体思路:
static函数/变量,仅在当前文件中可见,禁止外部链接。inline函数,这种函数由于需要允许重定义,属于一种特殊的“弱符号”,也不需要支持相关机制。这里体现的仍然不是inline作为“内联建议”的语义。- 局部变量,自然不会被链接。但是需要注意,直接包含在命名空间中的变量不属于这个范畴。
具体来讲,可以选择以下几种方案。
显式标注
为全部的全局函数/变量标注
CPPstatic,函数也可以选择标注 inline。二者效果在开启 O2 优化之后是相同的。static int a[10];
inline void f() {}
static int g() { return 0; }
匿名命名空间
不同于普通的具名命名空间,匿名命名空间中的所有元素相当于自动添加了一个
CPPstatic,仅在当前文件可见。namespace {
int a[10];
void f() {}
}
// 接下来可以直接使用 f(), a[i] 使用,无需特殊语法
成员函数
类的成员函数会自动添加
CPPinline,和显式添加 inline 的函数具有相同效果。class Solution {
int a[100];
void f() {}
public:
void solve() {}
};
int main() {
Solution s{}; // 自动清零数组
s.solve();
}
lambda 函数
lambda 函数本质上是一个成员函数,也可以解决这个问题。
希望以上内容可以结束“inline 是否有用”这个争议。——在洛谷的评测环境下确实有用,但是也有其他的替代方案。
回复
共 668 条回复,欢迎继续交流。
正在加载回复...