什么时候应该为函数/方法编写关键字“ inline”?


562

什么时候应该inline为C ++中的函数/方法编写关键字?

在看到一些答案之后,一些相关的问题:

  • 何时应写关键字“内联”在C ++函数/方法?

  • 编译器何时不知道何时使函数/方法“内联”?

  • 当一个应用程序为函数/方法写“内联”时,如果一个应用程序是多线程的,这有关系吗?


40
如果您在标头中定义函数,则需要内联声明它。否则,您将获得有关函数的多个定义的链接器错误。
马丁·约克

15
@Martin:除非是在类定义中,否则请保持谨慎。
David Thornley,2009年

20
@David:要特别挑剔,这仅是因为此类函数被隐式标记inline(9.3 / 2)。
Lightness Races in Orbit


另请参阅C ++常见问题解答中的内联函数。他们对内联有很好的处理。
jww

Answers:


882

哦,老兄,我的宠儿之一。

inline更像是staticextern不是指令告诉编译器内联你的函数。 externstaticinline是联动指令,几乎完全是由连接器,而不是编译器使用。

据说这inline向编译器提示您认为应该内联函数。1998年可能确实如此,但是十年后,编译器不需要这些提示。更不用说人类在优化代码方面通常是错误的,因此大多数编译器都会完全忽略“提示”。

  • static-变量/函数名称不能在其他翻译单元中使用。链接器需要确保它不会意外使用另一个翻译单元中的静态定义的变量/函数。

  • extern-在此翻译单元中使用此变量/函数名称,但如果未定义,请不要抱怨。链接器将对其进行排序,并确保所有尝试使用某个外部符号的代码都有其地址。

  • inline-此功能将在多个翻译单元中定义,不用担心。链接器需要确保所有翻译单元都使用变量/函数的单个实例。

注意:通常,声明模板inline是没有意义的,因为它们已经具有链接语义inline。但是,需要inline使用显式的专门化和模板实例化。


您问题的具体答案:

  • 什么时候应该为C ++中的函数/方法编写关键字“内联”?

    仅当您希望在标头中定义函数时。仅当函数的定义可以以多个翻译单位显示时才更准确。在头文件中定义小的(如在一个衬里中)函数是一个好主意,因为它为编译器提供了更多信息,可在优化代码时使用。这也增加了编译时间。

  • 什么时候不应该在C ++中为函数/方法编写关键字“内联”?

    不要仅仅因为您认为如果编译器将代码内联就可以更快地运行代码,就不要添加内联。

  • 编译器何时不知道何时使函数/方法“内联”?

    通常,编译器将比您做得更好。但是,如果编译器没有函数定义,则无法选择内联代码。在最大化优化的代码private中,无论是否要求,通常所有方法都内联。

    另外,要防止在GCC中内联,请使用__attribute__(( noinline )),在Visual Studio中请使用__declspec(noinline)

  • 当一个应用程序为函数/方法写“内联”时,如果一个应用程序是多线程的,那有关系吗?

    多线程不会以任何方式影响内联。


172
+1我在...(永远)中看到的内联的最佳描述。现在,我将剥夺您的利益,并在我对inline关键字的所有解释中使用它。
马丁·约克

6
@Ziggy,我想说的是编译器内联和inline关键字无关。不过,您有个正确的主意。通常,猜测通过内联将改善的内容非常容易出错。该规则的例外是一个班轮。
deft_code 2011年

4
这个答案使我有些困惑。您说的所有有关编译器能够更好地内联/不内联的内容。然后,您说应该在标头中放置一个内衬/小函数,并且编译器在没有函数定义的情况下不能内联代码。这些不是有点矛盾吗?为什么不将所有内容都放入cpp文件中,让编译器决定呢?
user673679 2013年

5
编译器将仅内联函数调用(如果在调用站点上有定义)。将所有功能保留在cpp文件中将限制内联到该文件。我建议在.h中内联定义一个小的内衬,因为编译速度的开销可以忽略不计,而且您几乎可以保证编译器可以内联该调用。我对编译器内联的观点是,它是优化的黑手艺,在此方面,您的编译器比您要好得多。
deft_code 2013年

8
每当我读到一些有关互联网累积知识的内容时,我都必须想到约翰·劳顿(John Lawton)的一句名言:具有讽刺意味的是,信息时代赋予了人们以新
IInspectable 2013年

60

我想用一个令人信服的示例为这个主题中的所有出色答案做出贡献,以消除所有剩余的误解。

给定两个源文件,例如:

  • inline111.cpp:

    #include <iostream>
    
    void bar();
    
    inline int fun() {
      return 111;
    }
    
    int main() {
      std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
      bar();
    }
  • inline222.cpp:

    #include <iostream>
    
    inline int fun() {
      return 222;
    }
    
    void bar() {
      std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
    }

  • 情况A:

    编译

    g++ -std=c++11 inline111.cpp inline222.cpp

    输出

    inline111: fun() = 111, &fun = 0x4029a0
    inline222: fun() = 111, &fun = 0x4029a0

    讨论内容

    1. 即使您应该对内联函数有相同的定义,如果不是这种情况,C ++编译器也不会对其进行标记(实际上,由于单独的编译,它无法对其进行检查)。确保这一点是您的责任!

    2. 链接器不会抱怨一个定义规则,如fun()被声明为inline。但是,由于inline111.cppfun()编译器处理的第一个转换单元(实际上是在调用),因此编译器会在inline111.cpp中实例化fun()第一个调用遇到的实例。如果编译器决定fun()从程序的任何其他位置(例如,inline222.cpp)扩展其调用,则对的调用fun()将始终链接到从inline111.cpp生成的其实例(对inline222.cppfun()内部的调用)可能还会在该翻译单元中产生一个实例,但它将保持未链接状态)。实际上,从相同的&fun = 0x4029a0打印输出中可以明显看出这一点。

    3. 最后,尽管inline建议编译器实际扩展单行代码fun(),但它完全忽略了您的建议,这很明显,因为fun() = 111在这两行中。


  • 情况B:

    编译 (注意反向顺序)

    g++ -std=c++11 inline222.cpp inline111.cpp

    输出

    inline111: fun() = 222, &fun = 0x402980
    inline222: fun() = 222, &fun = 0x402980

    讨论内容

    1. 本案例主张案例A中已讨论的内容

    2. 请注意一个重要的观点,如果你注释掉实际调用fun()inline222.cpp出注释cout语句来在inline222.cpp完全),那么,尽管你的翻译单元的编译顺序,fun()将被实例化后,它在第一次调用相遇inline111.cpp,从而使打印出的情况B作为inline111: fun() = 111, &fun = 0x402980


  • 案例C:

    编译 (注意-O2)

    g++ -std=c++11 -O2 inline222.cpp inline111.cpp

    要么

    g++ -std=c++11 -O2 inline111.cpp inline222.cpp

    输出

    inline111: fun() = 111, &fun = 0x402900
    inline222: fun() = 222, &fun = 0x402900

    讨论内容

    1. 正如这里所描述-O2优化编译鼓励以实实在在地扩大可内联函数(另请注意,-fno-inline默认不优化选项)。从此处的输出可以明显看出,fun()实际上已对in 进行了内联扩展(根据其在特定翻译单元中的定义),从而产生两种不同 fun()的输出。尽管如此,从相同的打印输出中可以明显看出(标准要求)只有一个全局链接的实例。fun() &fun

8
您的答案是关于语言为何将此类inline功能变为未定义行为的说明性文章。
R Sahu

您还应该添加编译和链接是分开的情况,每种情况.cpp都是其自己的翻译单元。最好为-flto启用/禁用添加案例。
syockit,

C ++参考明确指出:“如果在不同的转换单元中对具有外部链接的内联函数或变量(自C ++ 17起)进行了不同的定义,则该行为未定义。”。因此,您编写的内容是特定于GCC的,因为这是协调编译和链接过程的副作用。另外,请注意,这可能因版本而异。
Petr Fiedler

27

进行模板特化时,仍然需要显式内联函数(如果特化在.h文件中)


21

1)如今,几乎从来没有。如果内联函数是一个好主意,则编译器将在不需要您帮助的情况下执行该操作。

2)总是。参见#1。

(编辑以反映您将问题分为两个问题...)


是。内联只是对编译器的提示,可以无视您。如今,编译器可能比程序员更了解哪些函数最好内联。
Mark Byers

1
是的,但是没有那么重要-要内联的函数,它的主体必须在同一编译单元中(例如,在标头中)。这在C程序中不太常见。
Michael Kohne

1
定义非成员函数模板(也称为非静态函数模板)不需要内联。参见一个定义规则(3.2 / 5)。
deft_code

2
-1:inline仍然需要,例如在头文件中定义一个函数(并且在多个编译单元中内联这样的函数是必需的)。
Melebius

1
@Étienne特定于实现。根据标准,有一个定义规则,这意味着如果您天真地将函数定义包含在多个转换单元中,则会出现错误。但是,如果该函数具有inline说明符,则链接器会自动将其实例折叠为一个实例,并且不使用ODR。
Ruslan

12

什么时候不应该在C ++中为函数/方法编写关键字“内联”?

如果函数是在头部声明和定义.cpp文件,你应该写关键字。

编译器何时不知道何时使函数/方法“内联”?

没有这种情况。编译器无法使函数内联。它所能做的就是内联该函数的部分或全部调用。如果没有该函数的代码,它将无法执行此操作(在这种情况下,链接程序必须能够执行此操作)。

当一个应用程序为函数/方法写“内联”时,如果一个应用程序是多线程的,那有关系吗?

不,那没关系。


在某些情况下,在.cpp文件中使用内联是适当的。例如,将优化应用于完全实现特定的代码。
罗宾·戴维斯

@RobinDavies更新了答案。看来您误解了我想写的东西。
Johannes Schaub-litb

5
  • 编译器何时不知道何时使函数/方法“内联”?

这取决于所使用的编译器。不要盲目地相信当今的编译器比人类更懂得如何内联,并且出于性能原因,永远不要使用它,因为它是链接指令而不是优化提示。虽然我同意这些论点在意识形态上是正确的,但与现实相遇可能是另一回事。

在阅读了多个线程之后,出于好奇,我尝试了内联对我正在工作的代码的影响,结果是我得到了可测量的GCC加速,而我的Intel编译器没有加速。

(更多详细信息:很少有在类外部定义的关键函数的数学模拟,GCC 4.6.3(g ++ -O3),ICC 13.1.0(icpc -O3);向关键点添加内联会导致GCC代码加速+ 6%)。

因此,如果您将GCC 4.6认定为现代编译器,那么结果是,如果您编写CPU密集型任务并且知道瓶颈在哪里,那么内联指令仍然很重要。


6
我希望看到更多证据来支持您的主张。请提供您要测试的代码以及带有和不带有inline关键字的汇编输出。任何事情都可以给您带来性能上的好处。
void.pointer 2014年

1
最终,一个不仅重复别人的话,而且确实验证了这些陈述的人。Gcc确实确实仍将inline关键字视为提示(我认为clang完全忽略了它)。
MikeMB '16

@ void.pointer:为什么这么难以置信?如果优化程序已经完美,那么新版本将无法提高程序性能。但是他们经常这样做。
MikeMB '16

3

实际上,几乎没有。您所要做的只是建议编译器内联给定函数(例如,替换对该函数的所有调用/替换其主体)。当然,不能保证:编译器可能会忽略该指令。

编译器通常会很好地检测和优化这样的事情。


7
问题是在C ++中inline存在语义差异(例如,在处理多个定义的方式上),这在某些情况下(例如模板)很重要。
2009年

4
内联用于解决符号具有多个定义的情况。但是,模板已由该语言处理。一个例外是专门的模板函数,该模板函数不再具有任何模板参数(template <>)。它们比模板更像函数,因此需要inline关键字才能链接。
deft_code

2

默认情况下,在未启用优化的情况下进行编译时,gcc不会内联任何函数。我不了解Visual Studio – deft_code

我通过使用/ FAcs进行编译并查看汇编代码来检查Visual Studio 9(15.00.30729.01):编译器生成了对成员函数的调用,而未在调试模式下启用优化。即使该函数用__forceinline标记,也不会生成任何内联运行时代码。


1
启用/ Wall,以告知哪些功能已标记为内联但实际上并未内联
paulm 2014年

0

您想将其放在返回类型之前的最开始。但是大多数编译器都忽略了它。如果已定义它,并且具有较小的代码块,则大多数编译器无论如何都将其内联。


0

除非您正在编写库或有特殊原因,否则您可以忘记inline使用链接时间优化。它消除了必须在标头中包含函数定义的要求,以便可以考虑跨编译单元进行内联,而这正是inline允许的情况。

(但是请参见是否有任何理由不使用链接时间优化?


0

C ++内联与C内联完全不同。

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} //implicitly inline
  static inline int j = 3; //explicitly inline
};
int main() {
  c j;
  std::cout << i;
}

inline它本身会影响编译器,汇编器和链接器。这是对编译器的指令,如果在转换单元中使用了该函数/数据,则仅发出该函数/数据的符号;如果是,则类似于类方法,告诉汇编器将其存储在该节中.section .text.c::function(),"axG",@progbits,c::function(),comdat.section .bss.i,"awG",@nobits,i,comdat用于数据。

这是.section name, "flags"MG, @type, entsize, GroupName[, linkage]。例如,部分名称为.text.c::function()axG表示该节是可分配的,可执行的,并且在一个组中,即将指定一个组名(并且没有M标志,因此将不指定entsize);@progbits表示该部分包含数据且不为空;c::function()是组名,并且该组具有comdat链接意味着在所有目标文件中,带有此组名并用comdat标记的所有节都将从最终可执行文件中删除,除了1之外,即编译器确保翻译单元中只有一个定义,然后告诉汇编器放入将其放在目标文件中自己的组中(1组中的1个部分),然后链接器将确保,如果任何目标文件具有相同名称的组,则在最终.exe中仅包含一个。汇编器和链接器现在可以看到inline与不使用之间的区别inline,因为链接器因为它们的指令未将其存储在常规文件.data.text其他文件中。

static inline在类中,这意味着它是类型定义而不是声明(允许在类中定义静态成员)并使它内联;现在,它的行为如上所述。

static inlineat文件作用域仅影响编译器。这对编译器意味着:仅在此函数/数据在转换单元中使用时才发出此符号,并作为常规静态符号(不带.globl指令存储在.text /.data中)发出此符号。对于汇编程序,现在static和之间没有区别static inline

extern inline是一个声明,表示您必须在翻译单元中定义此符号或抛出编译器错误;如果已定义,则将其视为常规对象inline,对于汇编器和链接器,extern inline和之间没有区别inline,因此,这仅是编译器保护。

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

上面的所有内容(没有错误行)都折叠为inline int i[5]。显然,如果这样做了,extern inline int i[] = {5};extern由于通过赋值进行了明确的定义,因此将被忽略。

inline在一个命名空间,看看这个这个


0

内联关键字要求编译器用函数的主体替换函数调用,它首先对表达式求值,然后传递。由于不需要存储返回地址并且函数不需要堆栈内存,因此它减少了函数调用的开销。论点。

何时使用:

  • 改善绩效
  • 减少通话开销。
  • 由于这只是对编译器的请求,因此某些函数不会被内联*大函数
    • 有太多条件参数的函数
    • 递归代码和带循环的代码等

-1

在开发和调试代码时,请省去inline。它使调试复杂化。

添加它们的主要原因是为了帮助优化生成的代码。通常,这是以增加的代码空间为代价的,但是有时却inline节省了代码空间和执行时间。

在算法完成之前展开这种关于性能优化的想法就是过早的优化


12
inline除非使用优化进行编译,否则通常不会内联函数,因此它们不会以任何方式影响调试。请记住,这只是一个提示,而不是需求。
2009年

3
默认情况下,在未启用优化的情况下进行编译时,gcc不会内联任何函数。我不知道Visual Studio
deft_code

我从事了一个庞大的g ++项目,该项目启用了调试功能。也许其他选项阻止了它,但是inline内联了函数。在其中设置有意义的断点是不可能的。
wallyk

2
启用调试不会停止在gcc中内联。如果启用了任何优化(-O1或更高),则gcc将尝试内联最明显的情况。传统上,GDB很难处理断点和构造函数,尤其是内联构造函数。但是,该问题在最新版本中已得到解决(至少为6.7,可能更快)。
deft_code

2
添加inline将无助于改进现代编译器上的代码,现代编译器可以确定是否单独内联。
David Thornley,2009年

-1

什么时候应该内联:

1.当要避免函数调用时发生的事情开销如参数传递,控制传递,控制返回等。

2.函数应该小,经常调用,并且内联实际上是有优势的,因为按照80-20的规则,尝试使那些对程序性能有重大影响的函数内联。

众所周知,内联只是向编译器发出的类似于注册的请求,这将使您花费目标代码大小。


“内联只是对编译器的请求,类似于注册”。它们之所以相似,是因为它们都不是请求,也不与优化有关。inline已经失去了其作为优化提示的地位,并且大多数编译器仅将其用于允许多个定义-如IMO所愿。更重要的是,自C ++ 11起,由于register其“我比编译器更懂得如何优化”的先前含义而被完全弃用:它现在只是一个保留字,没有当前含义。
underscore_d

@underscore_d:Gcc仍然inline在某种程度上收听。
MikeMB '16

-1

C ++内联函数是类中常用的强大概念。如果函数是内联函数,则编译器将在编译时调用该函数的每个点上放置该函数代码的副本。

对内联函数的任何更改都可能需要重新编译该函数的所有客户端,因为编译器将需要再次替换所有代码,否则它将继续使用旧功能。

要内联函数,请将关键字inline放在函数名称之前,并在对函数进行任何调用之前定义函数。如果定义的函数超过一行,则编译器可以忽略内联修饰符。

即使不使用内联说明符,类定义中的函数定义也是内联函数定义。

下面是一个示例,该示例利用内联函数返回两个数的最大值

#include <iostream>

using namespace std;

inline int Max(int x, int y) { return (x > y)? x : y; }

// Main function for the program
int main() {
   cout << "Max (100,1010): " << Max(100,1010) << endl;

   return 0;
}

有关更多信息,请参见此处

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.