C++类方法在头文件中定义时是否总是内联的?

9

编辑:我已经恢复了原始标题,但是我真正应该问的是:“C++链接器如何处理在多个对象文件中定义的类方法”

假设我有一个在头文件中定义的 C++ 类的示例,代码如下:

class Klass
{
    int Obnoxiously_Large_Method()
    {
        //many thousands of lines of code here
    }
}

如果我编译一些使用“Obnoxiously_Large_Method”代码的C ++代码,会不会始终内联“Obnoxiously_Large_Method”的代码,并优化大小(例如,使用g ++ -Os),并创建一个“Obnoxiously_Large_Method”的单个实例并像常规函数一样使用它?如果是这样,链接器如何解决其他已实例化相同函数的对象文件之间的冲突?是否有某种奥秘的C++命名空间Juju,可以使方法的单独对象实例彼此不发生冲突?

5
请不要以这种方式编辑问题。这样会让人感到混乱,而且在你编辑后提供的所有答案都与你编辑后的问题无关。更好的方法是接受原来问题的答案,然后再提出新问题。请将此问题恢复为原来的状态,接受一个答案,然后开始一个新问题。 - John Dibling
6个回答

11

7.1.2函数说明符

带有内联说明符(inline specifier)的函数声明(8.3.5,9.3,11.4)声明了一个内联函数。内联说明符告诉编译器,在调用点处优先使用函数体的内联替换而不是通常的函数调用机制。编译器不一定需要在调用点执行此内联替换; 但即使省略此内联替换,仍将遵守由7.1.2定义的内联函数的其他规则。

因此,编译器不一定要实际“内联”任何函数。

然而,标准还说:

具有外部链接的内联函数在所有翻译单元中都必须具有相同的地址。

成员函数通常具有外部链接(一个例外是当成员函数属于一个'local'类时),因此内联函数必须具有唯一的地址,以便处理取函数地址的情况。在这种情况下,编译器将安排链接器放弃所有非内联副本的函数,并修复对函数的所有地址引用为保留的那个。


非常好的回答,这正是我想知道的。 - Gearoid Murphy

5

C++98标准的第9.3节,成员函数部分规定:

成员函数可以在其类定义中定义(8.4),在这种情况下,它是一个内联成员函数(7.1.2)。

因此,已经成为事实的是,在类定义中显式标记定义的成员函数为inline是不必要的。

关于 inline 函数说明符,标准规定:

带有inline说明符的函数声明(8.3.5,9.3,11.4)声明了一个内联函数。内联说明符指示[C++编译器]应优先选择在调用点内联替换函数体,而不是使用常规函数调用机制。[然而,C++编译器]不需要在调用点执行此内联替换;

因此,实际上是否内联函数的定义取决于编译器,而不是通过通常的函数调用机制调用它。


4

除非你的编译器有一个属性或私有关键字强制执行内联(这时你会写$(COMPILER)特定的C++而不是标准C++),不然没有任何函数总是能够被内联。一些非常长的函数,递归函数和其他一些东西通常也不能被内联。

如果编译器确定这样做会降低性能、不合理地增加目标文件的大小或者使事情工作不正确,或则它正在为大小而不是速度进行优化,或则你要求它不要这样做,或则它不喜欢你的衬衫,或则它今天感到懒惰,因为它昨晚编译了太多的东西,或则出于任何其他原因,或则毫无理由地,编译器可以选择不内联某些内容。


但如果它没有被内联,那么必须有一个方法的对象表示,如果是这样,链接器如何解决跨对象文件的多个此方法的实例? - Gearoid Murphy
1
@GearoidMurphy:从技术上讲,代码是重复的。但是你的链接器可能足够聪明,能够检测到完全相同的代码并去除重复项,这很容易弄清楚... - user405725
@cHao:内联函数可以(并且确实)具有外部链接。Vlad是正确的,目标代码是重复的,但必须明确指出代码是重复的,否则链接器会给出多个定义错误。inline的目的基本上是抑制链接器错误。 - deft_code
§7.1.2.4 *"......具有外部链接的内联函数在所有翻译单元中应具有相同的地址。......"*。这不仅说明了内联函数具有外部链接,而且还要求最终可执行文件中只有一个对象代码副本(其地址)。 - deft_code
示例代码不包括 inline,因此它不能成为关键字目的的一部分。编译器必须注意所有内联,而不管是否指定了 inline。至于其他方面,我改正了。 :) - cHao
显示剩余2条评论

2
这个问题没有一个单一的答案。编译器是聪明的生物。如果您想要,您可以专门使用内联单词,但这并不意味着编译器实际上会内联该函数。
内联存在是为了帮助开发人员进行优化。它提示编译器应该内联某些内容,但是这些提示通常被忽略,因为现在编译器可以更好地进行寄存器分配,并决定何时内联函数(事实上,编译器可以在不同的时间内内联或不内联函数)。在Ritchie发明C语言时,现代处理器的代码生成要复杂得多。
在C++中,这个词的意思是它可以有多个相同的定义,并且需要在每个使用它的翻译单位中定义它。(换句话说,您需要确保它可以内联。)您可以在头文件中拥有内联函数而没有任何问题,并且在类定义中定义的成员函数自动有效地内联。
话虽如此,我曾经使用过greenhills编译器,它实际上比违抗我的意愿更听从我的意愿 :).. 这真的取决于编译器。

1

它不必是内联的,不需要;就像您明确指定inline一样。

当您编写inline时,您承诺此方法不会从未定义它的翻译单元中调用,因此它可以具有内部链接(因此链接器不会将一个对象文件的引用连接到另一个对象文件的定义)。 [这段话是错误的。我保留它,只是划掉了,以便下面的评论仍然有意义。]


实际上,您不向编译器承诺任何内容,仍然可能获得该方法的地址或从其他翻译单元调用它。 - user405725
啊!原来就像静态关键字一样,这解释得很清楚,我之前没想到过,谢谢。 - Gearoid Murphy
@GearoidMurphy:不,它不是。这只是给编译器一个内联方法的提示。它可能会执行内联,也可能不会,如果您尝试获取此类方法的地址,则很可能会生成非内联版本。 - user405725
内联不是我所询问的,而是编译器如何处理跨对象文件的同一类方法的多个实例,我已更新问题以反映这一点。 - Gearoid Murphy
复制它们! :) - user405725

1

inline 关键字处理 C++ 函数的定义。编译器可以在任何地方内联对象代码。

定义为内联函数(例如使用 inline 关键字)的函数,在每个编译单元中创建函数的对象代码。这些函数被标记为特殊,因此链接器知道只使用一个。

有关更多详细信息,请参见 this answer


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接