内联函数具有非内联副本

时间:2015-12-05 20:49:14

标签: c++ gcc inline

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

g++ -O1 t1.cpp main.cpp怎么样? 由于foo未定义而无法编译。执行g++ -O1 t1.cppnm t1.o表示_Z3food已被删除。

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

似乎启用优化inline相当于static inline。但是没有优化inline意味着生成了非内联副本。

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

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

我的问题:

  1. 在启用优化的情况下,GCC是否正确剥离非内联副本?换句话说,如果我不使用static inline,应该总是有非内联副本吗?
  2. 如果我想确定没有非内联副本,我应该使用static inline吗?
  3. 我刚才意识到我可能误解了Agner的陈述。当他说函数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的非内联副本位于二进制文件中。

1 个答案:

答案 0 :(得分:6)

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

  

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

(N4140中的7.1.2 / 4)

这确实使你的问题中的例子格式不正确。

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

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