内联还有用吗? [复制]

Posted

技术标签:

【中文标题】内联还有用吗? [复制]【英文标题】:Is there still a use for inline? [duplicate] 【发布时间】:2015-06-30 00:49:14 【问题描述】:

我相信,inline 已经过时了,因为我读过here:

无论你如何将函数指定为inline,这是一个允许编译器忽略的请求:编译器可能内联扩展你调用指定为@的函数的部分、全部或任何地方987654330@.

但是,Angew 似乎理解了一些我不理解的东西。在this question他和我来回走了好久,关于inline是否还有用。

这个问题不是一个问题:

inlineinline 的历史用途仍可用于向编译器提示 inline 函数:When should I write the keyword 'inline' for a function/method?。 内联函数代码的优缺点:Benefits of inline functions in C++? 强制编译器使用inline 功能代码:force inline function in other translation unit

记住编译器可以随意inline,所以inline在那里没有帮助:哪里可以用inline强制,不建议,改变在编译代码中?

【问题讨论】:

@MatthieuM。我在发帖之前看了一下:***.com/questions/1759300/…,感觉这是一个关于inline对内联代码影响的问题。我特别声明我确实不想想知道这件事。我想知道inlines 的替代用途。在我看来,这两个问题非常不同。 好吧,我个人发现你的问题是重复的;因为一旦你确定inline 对暗示内联毫无用处,那么就只剩下语义,这正是“我什么时候应该为函数/方法编写关键字'内联'?”大约。但是,由于您的问题被重新提出,显然其他人也有同样的想法。请注意,如果您查看了其他问题,建议您将其链接并解释您认为您的问题不同的原因。 由于问题标题与实际问题严重不匹配,我投了反对票。你问,“内联还有用吗?”,答案是“是的,因为 ODR”,你还问,“inline 在哪里可以用来强制改变编译代码?” ,答案是“无处可去,除非您使用一些专门的编译器选项”。然后你说实际上你一直都知道第一件事,真正的问题是第二件事。 @MatthieuM。我添加了一个关于我查看的一些问题的部分,以及我觉得这个问题的不同之处。感谢您的建设性批评,我希望它最终提出了一个更好的问题。 @JonathanMee:好的,如果“是的,因为 ODR”是一个可以接受的答案,那么这是您链接到的问题的欺骗,因为答案是“无论何时需要ODR”。提出以前已经解决的问题的方式的微小变化不会产生新问题,如果您要问的是,“由于该答案中给出的原因,我们是否仍然需要使用内联2009 年,与 ODR 有什么关系?”,然后,“这仍然是真的吗?” IMO 是骗子吗? 【参考方案1】:

inline 现在主要只是一个外部链接说明符,出于您所说的原因。

所以是的,它确实有用,但它与实际的内联函数不同。它允许您在编译单元之间多次定义相同的方法并将它们正确链接在一起,而不是出现多个定义错误。

//header.h
inline void foo() 
void goo() 

//cpp1.cpp
#include "header.h"

//cpp2.cpp
#include "header.h"

// foo is okay, goo breaks the one definition rule (ODR)

实际上强制函数内联取决于编译器,有些可能通过特定的attributes 或pragmas 或(__forceinline)或诸如此类的支持。

简单地说,它允许您在标题中定义函数而不会破坏 ODR...

【讨论】:

为什么对外联动始终有效?是不是因为编译器/链接器看到定义错误会发生? @Panzercrisis:许多链接器可以支持“弱定义”的概念,这样如果符号被强定义,链接器将忽略任何弱定义并在第二个强定义时发出尖叫声,但如果它只是弱定义链接器将任意选择弱定义而不抱怨如果存在许多定义。这是一个非常有用的概念,也可以用来消除 API 差异(尽管它没有尽可能多地使用)。例如,在 FPU 寄存器中传递浮点值将比在主 CPU 寄存器中传递它们更快... ...在带有 FPU 的 ARM 上,但是对于没有 FPU 的 ARM,需要传入主 CPU 寄存器。可以通过指定接受 FPU 寄存器中的内容的函数必须使用更改的名称定义来使所有调用者使用所有方法,并使用未更改的名称弱定义一个函数,该函数将 CPU 寄存器加载到 FPU 寄存器并调用更改的版本。期望将事物传递给 FPU 寄存器中的另一个函数的代码将调用具有更改名称的函数,并弱定义具有更改名称的函数,将事物加载到 CPU regs 中...... ...并调用未更改的那个。对 FPU 一无所知的代码只会使用未更改的名称并期望 CPU 寄存器中的内容。在这种情况下,任何一种代码都可以调用任何一种方法。据我所知,在 FPU 使用方面,ARM 的 ABI 没有真正以这种方式工作,但这种方法通常有助于解决类似问题。 @supercat 我不确定我是否在关注您...您创建一个单独的问题并自己回答会不会太多?我认为你所说的一个例子真的很有帮助,因为我不确定我是否理解“弱定义”,所以接下来的一切都越来越模糊。【参考方案2】:

我会尽力解释我的“秘密理解”。

这里有两个完全不同的概念。一是编译器通过直接在调用点重复函数体来替换函数调用的能力。另一种是在多个翻译单元(=多个.cpp文件)中定义函数的可能性。

第一个称为函数内联。第二个是inline 关键字的用途。 从历史上看,inline 关键字也是对编译器的强烈建议,它应该内联标记为inline 的函数。随着编译器在优化方面变得更好,这个功能已经消退,使用inline 作为内联函数的建议确实已经过时了。如果编译器发现这是一个更好的优化,它会很高兴地忽略它并完全内联其他东西。

我希望我们已经处理了明确的inline–inlining 关系。当前代码中没有。

那么,inline 关键字的实际用途是什么?很简单:标记为inline 的函数可以在多个翻译单元中定义,而不会违反单一定义规则 (ODR)。想象一下这两个文件:

file1.cpp

int f()  return 42; 

int main()
 return f(); 

file2.cpp

int f()  return 42; 

这个命令:

> gcc file1.cpp file2.cpp

会产生一个链接器错误,抱怨符号f被定义了两次。

然而,如果你用inline 关键字标记一个函数,它会明确告诉编译器和链接器:“你们确保这个函数的多个相同定义不会导致任何错误!”

所以以下将起作用:

file1.cpp

inline int f()  return 42; 

int main()
 return f(); 

file2.cpp

inline int f()  return 42; 

将这两个文件编译和链接在一起不会产生任何链接器错误。

请注意,f 的定义当然不必逐字记录在文件中。它可以来自#included 头文件:

f.hpp

inline int f()  return 42; 

file1.cpp

#include "f.hpp"

int main()
 return f(); 

file2.cpp

#include "f.hpp"

基本上,要能够将函数定义写入头文件,必须将其标记为inline,否则会导致多次定义错误。


最后一个难题是:当关键字与内联无关时,为什么实际上拼写为inline?原因很简单:要内联一个函数(也就是说,通过在调用站点上重复它的主体来替换对它的调用),编译器必须首先拥有函数的主体。 p>

C++ 遵循一种单独的编译模型,其中编译器 无法访问它当前生成的目标文件以外的目标文件。因此,为了能够内联一个函数,它的定义必须是当前翻译单元的一部分。如果您希望能够将其内联到多个翻译单元中,则其定义必须在所有翻译单元中。通常,这会导致多重定义错误。因此,如果您将函数放在标头中并在任何地方都将其定义为#include 以在任何地方启用其内联,则必须将其标记为inline 以防止出现多个定义错误。

请注意,即使在今天,虽然编译器会内联任何认为合适的函数,但它仍然必须能够访问该函数的定义。因此,虽然 inline 关键字作为提示“请内联”不是必需的,但您可能仍会发现您需要使用它来启用编译器进行内联(如果它选择这样做)。没有它,您可能无法将定义放入翻译单元,而没有定义,编译器根本无法内联函数。

编译器不能。链接器可以。现代优化技术包括链接时间代码生成(也称为全程序优化),其中优化器在实际链接之前作为链接过程的一部分在所有目标文件上运行。在这一步中,所有函数定义当然都是可用的,并且完全可以内联,而无需在程序中的任何地方使用单个 inline 关键字。但是这种优化通常在构建时间上代价高昂,尤其是对于大型项目。考虑到这一点,仅依靠 LTCG 进行内联可能不是最佳选择。


为了完整起见:我在第一部分略有作弊。 ODR 属性实际上不是inline 关键字的属性,而是内联函数(这是语言的术语)的属性。内联函数的规则是:

可以在多个翻译单元中定义而不会导致链接器错误 必须在使用它的每个翻译单元中定义 它的所有定义必须是token-for-token和entity-for-entity相同的

inline 关键字将函数转换为内联函数。将函数标记为内联的另一种方法是直接在类定义中定义(而不仅仅是声明)它。这样的函数是自动内联的,即使没有 inline 关键字。

【讨论】:

答案最后部分的解释很好! 很好的解释,我仍然使用内联来处理程序调用很多次的非常小的函数。 OTOH,目前仔细手动调整内联仍然可以击败编译器(请参阅Andrei Alexandrescu's talk at CppCon 2014)。但是只有当你是 Facebook 或类似规模的公司时,小的性能提升才有意义,其中 0.3% 的性能损失是严重的倒退。 @PravasiMeet 不,它们实际上是相同的功能,这就是重点。它在所有.cpp 文件(=翻译单元)中具有相同的地址,并共享相同的静态变量(如果有)。它只是一个函数,但它的主体必须存在于所有引用它的翻译单元中(例如调用它或获取它的地址)。 @Angew:换句话说,可能内联在多个 .cpp 文件(翻译单元)中调用的函数(在调用站点上用其主体替换其调用) ,编译器需要每个.cpp文件中的函数定义,但链接器只允许程序中定义一个函数(一个定义规则),所以我们使用inline关键字绕过限制的链接器。底线:当在多个.cpp 文件中调用函数时,inline 关键字使 链接器 静默以允许函数内联。我说对了吗?

以上是关于内联还有用吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥模板特化需要内联定义? [复制]

是否可以创建内联伪样式? [复制]

为啥我不能在我的类中内联函数? [复制]

是否有任何忽略默认内联函数的 C++ 标准的编译器? [复制]

如何编写 :hover 使用内联样式? [复制]

如何内联 zcat 两个文件以执行差异? [复制]