【问题标题】:How does a compiler deal with inlined exported functions?编译器如何处理内联导出函数?
【发布时间】:2010-01-26 07:57:34
【问题描述】:

如果头文件包含函数定义,则编译器可以对其进行内联。如果函数被导出,函数的名称和实现也必须在链接期间对客户端可用。编译器如何实现这一点?它是否内联函数并为外部调用者提供实现?

考虑 Foo.h:

class Foo
{
    int bar() { return 1; }
};

Foo::bar 可能被内联或不在库 foo.so 中。如果另一段代码包含 Foo.h,它是否总是创建自己的 Foo::bar 副本,无论是否内联?

【问题讨论】:

    标签: c++ compiler-construction export inline


    【解决方案1】:

    头文件只是复制粘贴到源文件中——#include 就是这样做的。如果使用该关键字声明或在类定义中定义,则函数仅是 inline,而 inline 只是一个提示;它不会强制编译器生成不同的代码或禁止你做任何你可以做的事情。

    您仍然可以获取inline 函数的地址,或者正如您所提到的,将其导出。对于这些用途,编译器只是将其视为非inline 并使用One Definition Rule(表示用户不能将两个定义应用于同一函数、类等的规则)来“确保”该函数是定义一次,只导出一个副本。通常你只能在所有来源中有一个定义;内联函数必须有一个定义,该定义在它使用的每个源中都完全重复。

    以下是标准对 inline extern 函数 (7.1.2/4) 的规定:

    内联函数应定义在 它所在的每个翻译单元 使用并应具有完全相同的 每种情况下的定义(3.2)。 [笔记: 对内联函数的调用可能是 在定义之前遇到 出现在翻译单元中。 ] 如果 具有外部链接的函数是 在一个翻译中声明为内联 单位,它应该被声明为内联 它所在的所有翻译单元 出现;不需要诊断。一个 具有外部链接的内联函数 应具有相同的地址 翻译单位。静态局部 外部内联函数中的变量 总是指同一个对象。一种 外部内联中的字符串文字 函数是同一个对象 不同的翻译单元。

    【讨论】:

    • 我前段时间读到过,我没有断定编译器会忽略 inline 关键字。它仍然可以在所有调用位置内联代码,但生成非内联版本并将其用于地址。当然,这只是个人结论,我不确定它是否适用于任何编译器。另一件事是,我知道没有编译器实际上强制执行 de ODR,他们大多假设它 - 链接器将删除除一个定义之外的所有定义,并假设所有其他定义完全相同。
    • 顺便说一句,inline 关键字确实使编译器的行为有所不同,在段落末尾引用 7.1.2 [dcl.fct.spec]/2:'An在调用位置执行此内联替换不需要实现;但是,即使省略了此内联替换,仍应遵守 7.2.1 中定义的内联函数的其他规则。',这基本上是指您已经引用的内容-该函数将在所有翻译单元中定义,并且不会违反了 ODR。对于模板和内联函数,ODR 规则与常规函数不同
    • @David:我怀疑任何编译器都会完全忽略inline,但可以自行决定是正常调用inline 还是内联本地定义的extern。 7.1.2 唯一真正需要的是允许多个相同定义的替代 ODR。内联函数的地址很常见,所以我想大多数编译器在这个问题上都是 100% 兼容的。但这只是重述我已经说过的话……您是否不同意或有疑问……?
    • 我只是想澄清一下,虽然函数可以是非内联的,但编译器不能(字面意思)“忽略内联并使用单一定义规则”——“内联”的 ODR 不同'd 函数比非内联函数,所以我觉得这句话有点误导。特别是,编译器将生成内联函数的许多定义(标准要求它),然后在链接步骤中丢弃除一个之外的所有定义,我不知道有任何编译器实际执行规则(gcc 正在处理它) 内联/模板的所有定义都是相同的。无论如何都要 +1
    【解决方案2】:

    这通常意味着它最终会为每个在链接时使用它的 obj 文件创建一个单独的内联方法。它也可能会失败或拒绝内联许多东西,因此这可能会导致问题,因为您可能会遇到臃肿的 obj,而不会获得内联带来的性能优势。虚拟方法内联也会发生同样的事情,因此值得强制内联并为内联失败设置警告(关于编译器给出的唯一有用的警告消息)。

    【讨论】:

    • 标准要求extern内联函数在所有翻译单元中具有相同的地址,即在不同的obj文件中没有单独的副本。
    • address-of 运算符的结果在所有翻译单元中返回相同地址的事实并不意味着该函数不能内联。事实上,链接器可以内联未声明 inline 的函数,并且地址操作符应该仍然可以工作并在所有翻译单元中报告相同的(单个)地址。
    • 当它完成内联时,我不能 100% 说实话,但我确实知道在某些情况下,你可能会得到每个 obj 的单独副本的内联,尤其是当虚拟内联失败时。
    【解决方案3】:

    导出,我猜你的意思是获取指向函数的指针,然后通过指针调用函数。

    是的,在这种情况下,编译器将生成一个常规函数,以便可以从指针调用它。

    一种方法是使用一次链接部分。这个想法是,在翻译单元中,将代码放在具有基于函数名称的名称的特殊类型的部分中。在链接期间,链接器将只保留一个同名链接一次部分的实例。

    【讨论】:

      【解决方案4】:

      编译后的二进制文件中不存在内联函数:这是因为它们被直接获取并放置在调用站点(所谓的 IN-LINE)。内联函数的每次使用都会导致在该位置拉入完整的代码。

      因此无法导出内联函数,因为它们不存在。但是,如果您在一个标题中有定义,您仍然可以使用它们。是的,您必须为内联函数提供定义,否则您无法使用它。

      如果你设法导出了一个内联函数,那么它肯定不再是内联了:内联不是一个严格的语义元素。根据编译器和编译器设置,一个编译器可能选择内联,另一个不内联,有时提供警告,有时甚至是错误(我个人更愿意将其作为默认行为,因为它显示了发生意外事情的地方)

      【讨论】:

      • 当然存在内联函数。你可以取一个地址,extern inline 是完全有效且定义明确的。看我的回答。
      • -1 误导;例如,如果从 DLL 导出内联函数,编译器生成它的外联版本。
      • @Martin,这实际上就是我所说的“如果你设法导出了一个内联函数,那么它肯定不再是内联函数了”! :-)
      • @Potatoswatter,我认为标准定义的东西真的很臭,因为它可能会根据一个特定编译单元中发生的情况来做一个或另一个。也许这只是您所谓的内联定义的问题。对我来说,如果编译器生成一个函数,那么它就不再是内联的了。
      • @jdehaan:导出后它仍然是内联的。它在声明为内联的每个翻译单元中是内联的,并且必须在每个翻译单元中声明为内联。我不确定该标准对 DLL 的用途是什么,它们是否被认为是同一程序的更多翻译单元,但你当然可以在你的二进制文件中得到一个永远不会被直接调用的 extern 函数。
      猜你喜欢
      • 2011-08-06
      • 2010-10-17
      • 1970-01-01
      • 2016-12-09
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 2015-03-08
      相关资源
      最近更新 更多