内联函数有一个非内联副本
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.cpp
和 g++ t1.cpp
并查看带有 nm
的符号显示 main.o
中的 U _Z3food
和 t1.o
中的 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 的陈述。当他说 function inlinng 时,他可能指的是编译器倾斜代码而不是 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
方法的完整定义需要在使用它的每个翻译单元中可见:
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
关键字。
在 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.cpp
和 g++ t1.cpp
并查看带有 nm
的符号显示 main.o
中的 U _Z3food
和 t1.o
中的 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 的陈述。当他说 function inlinng 时,他可能指的是编译器倾斜代码而不是 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
方法的完整定义需要在使用它的每个翻译单元中可见:
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
关键字。