【问题标题】:Inlined functions have a non-inlined copy内联函数具有非内联副本
【发布时间】: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);
}

ma​​in.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.cppg++ t1.cpp 并查看带有nm 的符号在main.ot1.o 中显示U _Z3foodW _Z3food。所以很明显,Agner 声称存在非内联副本是正确的。

g++ -O1 t1.cpp main.cpp 怎么样? 由于foo 未定义,导致编译失败。 执行g++ -O1 t1.cppnm t1.o 表明_Z3food 已被剥离。

现在我很困惑。我没想到 g++ 会在启用优化的情况下删除非内联副本。

似乎启用优化后inline 等效于static inline。但是没有优化inline意味着生成了一个非内联副本。

也许 GCC 不认为我会想要非内联副本。但我能想到一个案例。假设我想创建一个库,并且在库中我想要在多个翻译单元中定义一个函数(以便编译器可以内联每个翻译单元中函数的代码),但我还想要一个链接到我的库的外部模块能够调用库中定义的函数。我显然需要一个非内联版本的函数。

如果我不想要非内联副本,Agner 给出的一个建议是使用static inline。但是从这个question and answers 我推断这仅对显示意图有用。因此,一方面很明显,它不仅仅是意图而不使用优化,因为它会生成非内联副本。但另一方面,通过优化,它实际上似乎只显示意图,因为非内联副本被剥离了。这令人困惑。

我的问题:

  1. GCC 在剥离非内联副本并启用优化方面是否正确?换句话说,如果我不使用static inline,是否应该始终存在非内联副本?
  2. 如果我想确定不存在非内联副本,我应该使用static inline吗?

我刚刚意识到我可能误解了 Agner 的说法。当他说函数内联时,他可能指的是编译器倾斜代码,而不是使用inline 关键字。换句话说,他可能指的是用extern 定义的函数,而不是inlinestatic

例如

//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 编译表明foobar 中是内联的,但从未使用过的foo 的非内联副本在二进制文件中。

【问题讨论】:

  • 我不知道标准是怎么说的,但对于常见情况来说是有意义的。通常 inline/constexpr 函数是在头文件中定义的,因此不会发生声明但未定义的情况。
  • @kamikaze,你能说得更具体点吗?你在“它有道理”中指的是什么......我不知道你所说的声明但未定义是什么意思。很抱歉我的问题太长了。
  • 我认为这样做的重点是您应该能够创建一个指向内联函数的指针(它将指向非内联版本),并且拥有该非内联版本的单个实例会导致生成较少代码以防多个翻译单元希望有一个指向“内联函数”的指针。
  • @Zboson 声明是当你有一个原型(即签名是已知的),如 double foo(double)。定义了带有主体的完整函数。
  • @kamikaze,谢谢,我明白你的意思了。

标签: c++ gcc inline


【解决方案1】:

标准规定inline 方法的完整定义需要在使用它的每个翻译单元中可见:

一个内联函数应该在每个使用它的翻译单元中定义,并且应该有 在每种情况下都具有相同的定义(3.2)。 [...]如果具有外部链接的功能是 在一个翻译单元中声明为 inline,则应在其出现的所有翻译单元中声明为 inline; 不需要诊断。

(N4140 中为 7.1.2/4)

这确实使您问题中的示例格式不正确。

此规则还包括链接您的库的任何外部模块中的每个 TU。他们还需要 C++ 代码中的完整定义,例如通过在标题中定义函数。因此,如果当前翻译不需要,编译器可以安全地省略任何类型的“非内联副本”

关于确定副本不存在:标准不保证任何优化,这取决于编译器。无论有没有额外的 static 关键字。

【讨论】:

  • 也许我当时没有问对问题。这是否意味着我的代码有问题?如果我也没有在main.cpp 中定义内联函数,我是否应该从main.cpp 调用foo。我就是这样读到你的答案的。我的代码示例一定是错误的。
  • @Zboson 是的,确实是错的。我添加了一个标准报价。
  • 并且 Agner 写道“如果没有其他模块调用该函数,这个非内联副本就是死代码。”但是为什么其他模块会调用这个函数,除非它是在同一个翻译单元中定义的?
  • 好的,谢谢,我想我希望您在回答中指出这一点。我想我仍然不知道何时使用static inline 与仅使用inline。如果代码正确,我看不出它在实践中会有什么不同。
  • @Zboson inline 函数仍然具有外部链接,除非声明为 static。在这种情况下,“具有外部链接的内联函数在所有翻译单元中应具有相同的地址。”
猜你喜欢
  • 1970-01-01
  • 2017-10-26
  • 2011-01-24
  • 2020-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多