2011-08-28 137 views
14

C++链接器是否会自动内联“传递”函数,这些函数在头文件中没有定义,并且没有明确要求通过inline关键字“内联”?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(); 
} 

我知道的MSVS连接器将通过其链接时代码生成(LTGCC)执行一些“内联”,那GCC工具链也支持链接时间优化(LTO)(见:Can the linker inline functions?)。另外,我知道有些情况下这个不能被“内联”,比如当实现对链接器不可用时(例如,跨越共享库边界,发生单独链接) 。

但是,如果这是代码链接到不交叉DLL /共享-lib的边界的单个可执行,我期望编译器/链接器厂商自动内联的功能,作为一个简单明显的优化(兼顾性能和体积)?

我的希望太天真了吗?

+0

我不知道这里是否内联发生(我是积极的可以),但它不是一个明显的*** omtimization。太多的内联可以实际上减慢你的程序的速度 –

+1

但是,在这种情况下,不是'inline' *总是*更小更快的结果?这不是一个简单的“函数调用 - 展开”,它不会在文本段中强制分页,并且不会增加堆栈的开销? (这只是一个嵌套函数调用的“展开”,*必须*仍然会从父上下文中调用。) – charley

+1

@charley如果调用某个函数所需的指令大于该函数,那么它将始终更小并且更快。这就是为什么如果你有优化,编译器几乎肯定会内联。 –

回答

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 

调用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> 

这是入门序幕留在原处,而是立即撤消(在leave指令)和代码跳转到妙:: foo的()做实际的工作。但是,这是编译器(不是链接器)正在执行的优化,因为它意识到MyB::foo()只是返回任何MyA::foo()返回的结果。我不知道为什么序幕在左

MSVC 16(从2010年VS)处理事情有点不同:

MyB::foo()结束了两个跳跃 - 一个为某种“咚”:

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

而形实转换只是跃升至MyA::foo()

myb!ILT+0(?fooMyAQBEHXZ): 
001a1005 e936000000  jmp  myb!MyA::foo (001a1040) 

再次 - 这主要是(?完全)编译器执行,因为我如果你看链接之前产生的目标代码,MyB::foo()被编译成普通跳转到MyA::foo()

所以熬这一切下来 - 它看起来像没有明确调用LTO/LTCG,今天的接头是不愿意/无法执行完全移除调用MyB::foo()的优化,即使MyB::foo()是一个简单的跳转到MyA::foo()。所以我想如果你想链接时间优化,使用-flto(对于GCC)或/GL(对于MSVC编译器)和/LTCG(对于MSVC链接器)选项。

+1

+1努力展示汇编并清楚地解释发生了什么。这非常有用..我想通过检查程序集来检查我的代码发生了什么。上述转储是如何产生的? – cppcoder

+0

+1也 - 谢谢*非常*这个。有趣的是,编译器可以减少这种情况(有意义),特别是在链接器不能/不会减少它的情况下......看到这些优化被添加到供应商编译器/链接器链中未来... – charley

+1

@charley:我认为编译器供应商认为已经添加了优化 - 但您必须使用链接时间优化选项才能获得它们。我认为这是一个合理的观点。 –

0

是的,如果你有适当的优化标志设置并且编译器认为它是一个性能奖励,任何像样的编译器都可以完全内联这个函数。

如果您确实想知道,请在函数调用之前添加一个断点,编译您的程序并查看程序集。如果你这样做会很清楚。

+0

有能力 - 是的。但是,这通常是“完成”的,对吗?这是'gcc'和'MSVS'中的默认行为? (请说“是”?) – charley

+0

@charley我不是编译器编写者,所以我只能说“这不会是愚蠢的”,除非编译器有很好的理由。像MSVC++和gcc这样的编译器是_extremely_ smart。所以我会说是的,但我不能证明这一点。只需编译你的程序并看看程序集。 –

+1

不管编译器有多聪明,如果它编译每个翻译单元都是孤立的,它将不能跨越它们。 – jalf

0

编译代码必须能够看到函数的内容以便内联。通过使用统一文件和LTCG,可以更好地发生这种情况。

-1

这里是我的什么编译器将功能做到理解:

如果函数定义在类定义内,并假设没有场景能防止“内联-ing”的功能,如递归,存在,该函数将是“内联-d”。

如果函数定义不在类定义之外,函数将不会是“内联-d”,除非函数定义明确包含inline关键字。

下面是艾弗霍顿开始的Visual C++ 2010的摘录:

内联函数

利用内联函数,编译器试图扩大在函数体代码代替通话,以功能。这避免了调用该函数的大部分开销,并因此加快了代码的速度。

编译器可能并不总是能够插入一个用于内联函数(例如使用递归函数或您已获得的地址的功能)的代码,但一般地,将工作。它最适用于非常简短的函数,比如CBox类中的Volume(),因为这样的函数执行速度更快,插入主体代码并不会显着增加可执行模块的大小。

随着类定义之外函数定义,编译器将功能作为一个正常的功能,并且该功能的调用将在通常的方式工作;但是,也可以告诉编译器,如果可能的话,您希望函数被视为内联。只需将关键字内联放置在函数头的开头即可完成。所以,对于这个功能,定义如下:

inline double CBox::Volume() 
{ 
    return l * w * h; 
} 
+1

如果它不在类定义中,是什么让你认为它不会被内联?这意味着它不能内联非成员函数。 –

+0

也许我不清楚,我可能是错的,我只按照我在一本书中读到的内容去做。但是,如果您声明非成员函数并在函数头中内联使用该工作,它将是内联的。但我不相信编译器会自动内联一个函数,除非它是在类中声明和定义的,或者除非使用了关键字inline。也许编译器优化可能会自动执行此操作,但是如果他希望确定函数是内联的,则最好声明它,因为编译器无法保证它会这样做。 – 2011-08-28 21:41:10

+0

实际上,即使您将_do_声明为“inline”,也不能保证编译器会执行此操作。所以基本上编译器几乎完全忽略了那个关键字。它相当于让它对内联进行三次思考,但没有更多。 –

8

这要看。大多数编译器(连接器,真的)都支持这种优化。但为了完成这些工作,整个代码生成阶段几乎必须推迟到链接时间。 MSVC调用选项链接时间代码生成(LTCG),并且在发布版本IIRC中默认启用它。

GCC也有类似的选项,根据不同的名称,但我不记得是哪-O水平,如果有的话,能使用它,或者如果它有明确启用。然而,“传统上”,C++编译器已经编译了一个单独的翻译单元,在隔离之后,链接器仅仅捆绑了松散的末端,确保当翻译单元A调用翻译单元B中定义的函数时,查找正确的功能地址并将其插入到调用代码中。

如果按照这种模式,那么就不可能在另一个转换单元中定义的内联函数。

这并不是说可以做“对飞”一样,比方说,循环展开只是一些“简单”的优化。它要求链接器和编译器合作,因为链接器将不得不接管编译器正常完成的一些工作。

请注意,编译器高兴地内联没有标记inline关键字的函数。但只有当它知道如何在被调用的站点上定义该函数。如果它看不到定义,那么它不能内联该呼叫。这就是为什么你通常在头文件中定义这样一些小的“意图内置”函数,使得它们的定义对所有调用者都是可见的。

+0

GCC有'fwhole-program'开关,我不认为它是由任何O级定义的。 – GManNickG

+0

'-fwhole-program'选项只能在有限的情况下工作,并且不会与示例模块集合一起工作(除非可能的话,如果函数被标记了正确的属性,将会失败在'-fwhole-program'中使用这个案例)。 –

+1

在现代GCC中,你必须用'-flto'(链接时间优化)编译每个阶段。链接阶段显着减慢,但我经常会得到20%的更小的可执行文件。 –

0

inline关键字仅作为编译器在进行优化时内联函数的指导。在g ++中,优化级别-O2和-O3会生成不同级别的内联。 g ++文档指定如下:(i)如果指定了O2 -finline-small-functions被打开(ii)如果指定了O3 -finline-functions与O2的所有选项一起打开。 (iii)然后还有一个相关选项“no-default-inline”,只有在添加了“inline”关键字时,才会使成员函数内联。

通常,如果使用递归调用,函数的大小(程序集中的指令数)决定是否发生内联。有很多在下面的链接中定义的G ++更多的选择:

http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

请看一看,看看你正在使用哪些,因为最终你使用的选项决定你的函数是内联。

5

内联不是链接器功能。

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

这不是默认完成的,您必须通过适当的命令行选项向编译器和链接器显式请求。

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

11

这是常见的吗?是的,适用于主流编译器。

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

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

gcc工具链中使用的传统链接器是ld,它有点笨。因此,这可能令人惊讶,链接时优化不是由gcc工具链中的链接器执行的。

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

当调用链接器的驱动程序(注:不链接直接)与-flto,驱动程序将读取这些特定的部分,捆绑在一起,变成一大块,喂这种捆绑的编译器。编译器重新应用优化,就像它通常为常规编译所做的那样(常量传播,内联,并且这可能为无效代码消除,循环转换等等提供新的机会......)并产生一个单一的大对象文件。

这个大的目标文件最终被馈送到工具链的常规链接器(可能是ld,除非你正在试验黄金),它会执行链接器的魔法。

Clang的工作原理类似,我猜测MSVC使用类似的技巧。

+0

g ++'和'[go] ld'如何处理这个问题。 –

+0

@BenVoigt:谢谢,编辑于。 –