内联函数有一个非内联副本

Inlined functions have a non-inlined copy

在 Agner Fog 的 Optimizing C++ manual 中,他有一个部分 "Inlined functions have a non-inlined copy" 他写道

Function inlining has the complication that the same function may be called from another module. The compiler has to make a non-inlined copy of the inlined function for the sake of the possibility that the function is also called from another module. This non-inlined copy is dead code if no other modules call the function. This fragmentation of the code makes caching less efficient.

我们来做个测试。

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.cppg++ t1.cpp 并查看带有 nm 的符号显示 main.o 中的 U _Z3foodt1.o 中的 W _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. ,是否应该始终有一个非内联副本
  3. 如果我想确定没有非内联副本,我应该使用 static inline 吗?

我刚刚意识到我可能误解了 Agner 的陈述。当他说 function inlinng 时,他可能指的是编译器倾斜代码而不是 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 编译显示 foo 内联在 bar 中,但从未使用过的 foo 的非内联副本位于二进制文件中。

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

An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case (3.2). [...] If a function with external linkage is declared inline in one translation unit, it shall be declared inline in all translation units in which it appears; no diagnostic is required.

(N4140 中的 7.1.2/4)

这确实构成了您问题中的示例 ill-formed。

此规则还包括链接您图书馆的任何外部模块的每个 TU。他们还需要 C++ 代码中的完整定义,例如通过在 header 中定义函数。因此,如果当前翻译不需要它,编译器可以安全地省略任何类型的 "non-inlined copy"

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