【发布时间】:2016-03-10 16:56:33
【问题描述】:
在 Agner Fog 的 Optimizing C++ manual 中,他写道:“内联函数具有非内联副本”一节
函数内联的复杂之处在于可以从另一个模块调用相同的函数。编译器必须制作内联函数的非内联副本,以便该函数也可以从另一个模块调用。如果没有其他模块调用该函数,则此非内联副本是死代码。这种代码碎片会降低缓存效率。
让我们对此进行测试。
foo.h
inline double foo(double x) {
return x;
}
t1.cpp
#include "foo.h"
double t1(double x) {
return foo(x);
}
main.cpp
#include <stdio.h>
extern double foo(double);
int main(void) {
printf("%f\n", foo(3.14159));
}
用g++ t1.cpp main.cpp 编译,它运行正常。如果我执行g++ -S t1.cpp main.cpp 并查看程序集,我会看到main.s 调用了t1.s 中定义的函数。执行g++ -c main.cpp 和g++ t1.cpp 并查看带有nm 的符号在main.o 和t1.o 中显示U _Z3food 和W _Z3food。所以很明显,Agner 声称存在非内联副本是正确的。
g++ -O1 t1.cpp main.cpp 怎么样? 由于foo 未定义,导致编译失败。 执行g++ -O1 t1.cpp 和nm t1.o 表明_Z3food 已被剥离。
现在我很困惑。我没想到 g++ 会在启用优化的情况下删除非内联副本。
似乎启用优化后inline 等效于static inline。但是没有优化inline意味着生成了一个非内联副本。
也许 GCC 不认为我会想要非内联副本。但我能想到一个案例。假设我想创建一个库,并且在库中我想要在多个翻译单元中定义一个函数(以便编译器可以内联每个翻译单元中函数的代码),但我还想要一个链接到我的库的外部模块能够调用库中定义的函数。我显然需要一个非内联版本的函数。
如果我不想要非内联副本,Agner 给出的一个建议是使用static inline。但是从这个question and answers 我推断这仅对显示意图有用。因此,一方面很明显,它不仅仅是意图而不使用优化,因为它会生成非内联副本。但另一方面,通过优化,它实际上似乎只显示意图,因为非内联副本被剥离了。这令人困惑。
我的问题:
- GCC 在剥离非内联副本并启用优化方面是否正确?换句话说,如果我不使用
static inline,是否应该始终存在非内联副本? - 如果我想确定不存在非内联副本,我应该使用
static inline吗?
我刚刚意识到我可能误解了 Agner 的说法。当他说函数内联时,他可能指的是编译器倾斜代码,而不是使用inline 关键字。换句话说,他可能指的是用extern 定义的函数,而不是inline 或static。
例如
//foo.cpp
int foo(int x) {
return x;
}
float bar(int x) {
return 1.0*foo(x);
}
和
//main.cpp
#include <stdio.h>
extern float bar(int x);
int main(void) {
printf("%f\n", bar(3));
}
使用gcc -O3 foo.cpp main.cpp 编译表明foo 在bar 中是内联的,但从未使用过的foo 的非内联副本在二进制文件中。
【问题讨论】:
-
我不知道标准是怎么说的,但对于常见情况来说是有意义的。通常 inline/constexpr 函数是在头文件中定义的,因此不会发生声明但未定义的情况。
-
@kamikaze,你能说得更具体点吗?你在“它有道理”中指的是什么......我不知道你所说的声明但未定义是什么意思。很抱歉我的问题太长了。
-
我认为这样做的重点是您应该能够创建一个指向内联函数的指针(它将指向非内联版本),并且拥有该非内联版本的单个实例会导致生成较少代码以防多个翻译单元希望有一个指向“内联函数”的指针。
-
@Zboson 声明是当你有一个原型(即签名是已知的),如 double foo(double)。定义了带有主体的完整函数。
-
@kamikaze,谢谢,我明白你的意思了。