在Agner Fog的优化C++手册中,他有一节"内联函数有一个非内联副本",他写道
函数内联具有复杂性,即可以从另一个模块调用相同的函数.编译器必须制作内联函数的非内联副本,以便可以从另一个模块调用该函数.如果没有其他模块调用该函数,则此非内联副本为死代码.代码碎片化使得缓存效率降低.
让我们为此做一个测试.
foo.h中
inline double foo(double x) { return x; }
t1.cpp
#include "foo.h" double t1(double x) { return foo(x); }
main.cpp中
#includeextern 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
显示U _Z3food
在main.o
和W _Z3food
中t1.o
.所以很明显,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
.但是从这个问题和答案我推断,这只是为了显示意图.所以一方面很明显它不仅仅是意图而不使用优化,因为它创建了非内联副本.但另一方面,通过优化,它实际上似乎只显示了意图,因为非内联副本被剥离了.这令人困惑.
我的问题:
在启用优化的情况下,GCC是否正确剥离非内联副本?换句话说,如果我不使用,应该总是有非内联副本static inline
吗?
如果我想确定我不应该使用非内联副本static inline
?
我才意识到我可能误解了Agner的陈述.当他说函数inlinng时,他可能会向编译器倾斜代码而不是使用inline
关键字.换句话说,他可能指的是extern
用inline
或不用或定义的函数static
.
例如
//foo.cpp int foo(int x) { return x; } float bar(int x) { return 1.0*foo(x); }
和
//main.cpp #includeextern float bar(int x); int main(void) { printf("%f\n", bar(3)); }
使用内联的gcc -O3 foo.cpp main.cpp
节目foo
进行编译,bar
但是foo
从未使用过的非内联副本在二进制文件中.
该标准规定,方法的完整定义inline
需要在每个使用它的翻译单元中可见:
内联函数应在每个使用过的翻译单元中定义,并且在每种情况下都应具有完全相同的定义(3.2).[......]如果在一个翻译单元中内联宣布具有外部联系的函数,则应在其出现的所有翻译单元中内联声明; 无需诊断.
(N4140中的7.1.2/4)
这确实使你的问题中的例子形成了错误.
此规则还包括链接库的外部模块中的每个TU.他们还需要C++代码中的完整定义,例如通过在头文件中定义函数.因此,如果当前转换不需要,编译器可以安全地省略任何类型的"非内联副本".
关于确定副本不存在:标准不保证任何优化,因此由编译器决定.有和没有额外的static
关键字.