C++链接器会自动内联函数(没有"inline"关键字,而不在头文件中实现)?

cha*_*ley 15 c++ optimization linker inline compiler-optimization

C++链接器是否会自动内联"传递"函数,这些函数未在头文件中定义,并且未明确要求通过inline关键字"内联" ?

例如,以下情况经常发生,并且应始终受益于"内联",似乎每个编译器供应商都应该通过链接器"内联""自动"处理它(在可能的情况下):

//FILE: MyA.hpp
class MyA
{
  public:
    int foo(void) const;
};

//FILE: MyB.hpp
class MyB
{
  private:
    MyA my_a_;
  public:
    int foo(void) const;
};

//FILE: MyB.cpp
// PLEASE SAY THIS FUNCTION IS "INLINED" BY THE LINKER, EVEN THOUGH
// IT WAS NOT IMPLICITLY/EXPLICITLY REQUESTED TO BE "INLINED"?
int MyB::foo(void)
{
  return my_a_.foo();
}
Run Code Online (Sandbox Code Playgroud)

我知道MSVS链接器将通过其链接时间代码生成(LTGCC)执行一些"内联" ,并且GCC工具链还支持链接时间优化(LTO)(请参阅: 链接器内联函数能否?).

此外,我知道有些情况下不能 "内联",例如当实现对链接器"不可用"时(例如,跨共享库边界,发生单独链接).

但是,如果这是代码链接到一个单一的,不跨越DLL /共享-lib的边界可执行文件,我希望编译器/连接器供应商,以自动内联函数,作为一个简单的和显而易见的优化(使双方获益的性能-和大小)?

我的希望太天真了吗?

Mic*_*urr 18

这是对您的示例的快速测试(具有MyA::foo()简单返回的实现42).所有这些测试都是使用32位目标 - 64位目标可能会出现不同的结果.值得注意的是,使用-flto选项(GCC)或/GL选项(MSVC)可以实现完全优化 - 无论何处MyB::foo()调用,它都会被替换为42.

使用GCC(MinGW 4.5.1):

gcc -g -O3 -o test.exe myb.cpp mya.cpp test.cpp
Run Code Online (Sandbox Code Playgroud)

对MyB :: foo()的调用没有被优化掉.MyB::foo()本身略微优化为:

Dump of assembler code for function MyB::foo() const:
   0x00401350 <+0>:     push   %ebp
   0x00401351 <+1>:     mov    %esp,%ebp
   0x00401353 <+3>:     sub    $0x8,%esp
=> 0x00401356 <+6>:     leave
   0x00401357 <+7>:     jmp    0x401360 <MyA::foo() const>
Run Code Online (Sandbox Code Playgroud)

哪个入口序言留在原地,但立即撤消(leave指令)和代码跳转到MyA :: foo()来完成真正的工作.但是,这是编译器(而不是链接器)正在进行的优化,因为它意识到MyB::foo()只返回任何MyA::foo()返回.我不确定为什么要留下序幕.

MSVC 16(来自VS 2010)处理的事情略有不同:

MyB::foo() 结果是两次跳跃 - 一次到某种'thunk':

0:000> u myb!MyB::foo
myb!MyB::foo:
001a1030 e9d0ffffff      jmp     myb!ILT+0(?fooMyAQBEHXZ) (001a1005)
Run Code Online (Sandbox Code Playgroud)

thunk只是跳到MyA::foo():

myb!ILT+0(?fooMyAQBEHXZ):
001a1005 e936000000      jmp     myb!MyA::foo (001a1040)
Run Code Online (Sandbox Code Playgroud)

再次 - 这在很大程度上(完全?)由编译器执行,因为如果你看一下链接之前产生的目标代码,MyB::foo()就编译成一个普通的跳转MyA::foo().

所以要把所有这些都烧掉 - 看起来没有明确地调用LTO/LTCG,今天的链接器不愿意/无法执行MyB::foo()完全删除调用的优化,即使MyB::foo()是简单的跳转MyA::foo().

所以我想如果你想要链接时间优化,可以使用-flto(对于GCC)或/GL(对于MSVC编译器)和/LTCG(对于MSVC链接器)选项.


Mat*_* M. 11

这很常见吗?是的,对于主流编译器.

它是自动的吗?一般不是.MSVC需要/GL开关,gcc和clang -flto标志.

它是如何工作的 ?(仅限gcc)

gcc工具链中使用的传统链接器是ld,它有点愚蠢.因此,令人惊讶的是,链接时优化不是由gcc工具链中的链接器执行的.

Gcc有一个特定的中间表示,在其上执行与语言无关的优化:GIMPLE.使用-flto(激活LTO)编译源文件时,它会将中间表示保存在目标文件的特定部分中.

当调用链接器驱动程序(注意:不是直接链接器)时-flto,驱动程序将读取这些特定部分,将它们捆绑到一个大块中,并将此包提供给编译器.编译器重新应用优化,因为它通常用于常规编译(常量传播,内联,这可能会为死代码消除,循环转换等提供新机会......)并生成单个大对象文件.

这个大目标文件最终被提供给工具链的常规链接器(可能是ld,除非你正在尝试使用黄金),这会实现其链接器魔法.

Clang的工作方式与此类似,我推测MSVC使用类似的技巧.


jal*_*alf 8

这取决于.大多数编译器(链接器,真的)支持这种优化.但是为了完成它,整个代码生成阶段几乎必须推迟到链接时.MSVC调用选项链接时代码生成(LTCG),默认情况下在发布版本IIRC中启用它.

GCC有一个类似的选项,名称不同,但我不记得哪个-O级别(如果有的话)启用它,或者是否必须明确启用它.

然而,"传统上",C++编译器编译的单个翻译单元中隔离,在此之后,链接器仅捆绑了松散端部,从而确保当翻译单元A呼叫在翻译单元B中定义的函数,正确的函数地址搜索up并插入到调用代码中.

如果您遵循此模型,则无法内联在另一个翻译单元中定义的函数.

这不仅仅是一些可以"即时"完成的"简单"优化,比如循环展开.它需要链接器和编译器协作,因为链接器必须接管编译器通常完成的一些工作.

注意,编译器未打上欣然内联函数 inline关键字.但只有当它知道如何在调用函数的站点定义函数时.如果它看不到定义,则无法内联调用.这就是为什么你通常在标题中定义如此小的简单"有意内联"函数,使得它们的定义对所有调用者都可见.


Ben*_*igt 5

内联不是链接器函数.

支持整个程序优化(跨TU内联)的工具链通过不实际编译任何东西来实现,只是在编译时解析和存储代码的中间表示.然后链接器调用编译器,它执行实际的内联.

默认情况下不会这样做,您必须使用编译器和链接器的相应命令行选项显式请求它.

它不是也不应该是默认的一个原因是它会大大增加基于依赖性的重建时间(有时会增加几个数量级,具体取决于代码组织).