我应该何时为函数/方法编写关键字'inline'?

时间:2009-11-18 21:46:01

标签: c++ inline one-definition-rule

我应该何时在C ++中为函数/方法编写关键字inline

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

  • 我应该何时为C ++中的函数/方法编写关键字'inline'?

  • 编译器什么时候不知道何时使函数/方法'内联'?

  • 如果某个应用程序为函数/方法写入'inline',多线程是否重要?

15 个答案:

答案 0 :(得分:774)

哦,男人,我的一个小小的烦恼。

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

据说inline向编译器提示您认为函数应该内联。这可能是在1998年,但十年后编译器不需要这样的提示。更不用说人类在优化代码时通常是错误的,所以大多数编译器都会忽略“提示”。

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

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

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

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


您的问题的具体答案:

  •   

    我应该何时在C ++中为函数/方法编写关键字'inline'?

    仅当您希望在标题中定义函数时。更确切地说,只有当函数的定义可以显示在多个翻译单元中时。最好在头文件中定义小(如在一个线性中)函数,因为它为编译器提供了更多信息,以便在优化代码时使用。它还会增加编译时间。

  •   

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

    不要仅因为您认为如果编译器内联它会使代码运行得更快而添加内联。

  •   

    编译器什么时候不知道何时使函数/方法'内联'?

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

    除了防止在GCC中内联之外,请使用__attribute__(( noinline )),并在Visual Studio中使用__declspec(noinline)

  •   

    当一个应用程序为函数/方法写'内联'时是否是多线程的是否重要?

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

答案 1 :(得分:45)

我想通过一个令人信服的例子来为这个主题中的所有重要答案做出贡献,以驱散任何剩余的误解。

给出两个源文件,例如:

  • <强> 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.cpp 是编译器处理的第一个翻译单元(实际上调用fun()),编译器会在其首先上实例化fun() inline111.cpp 中的来电相遇。如果编译器决定在程序中的任何其他位置(例如来自 inline222.cpp )中展开fun(),则对fun()的调用始终会链接到由 inline111.cpp 生成的实例(对 inline222.cpp 内部的fun()的调用也可能会产生该翻译单元中的实例,但它将保持不链接状态)。实际上,从相同的&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. 请注意,如果您在 inline222.cpp 中注释掉fun()的实际调用(例如注释掉{{1}完全在 inline222.cpp 中的语句然后,尽管您的翻译单元的编译顺序,cout将在 inline111.cpp中首次调用遇到时实例化,导致案例B 的打印输出为fun()

  • 案例C:

    编译 (注意-O2)

    inline111: fun() = 111, &fun = 0x402980

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

    输出

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

    讨论

    1. described here一样,inline111: fun() = 111, &fun = 0x402900 inline222: fun() = 222, &fun = 0x402900 优化鼓励编译器实际扩展可以内联的函数(另请注意-O2 默认< / em>没有优化选项)。从此处的outprint明显可见,-fno-inline实际上已经内联展开(根据其在特定翻译单元中的定义),导致两个不同的 fun()打印输出。尽管如此,仍然只有一个全局链接的fun()实例(根据标准的要求),从相同的 fun()打印中可以看出-out。

答案 2 :(得分:27)

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

答案 3 :(得分:18)

1)如今,几乎没有。如果内联函数是个好主意,编译器将在没有你帮助的情况下完成它。

2)总是。见#1。

(编辑反映你将问题分成两个问题......)

答案 4 :(得分:11)

  

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

如果在.cpp文件中定义了该函数,则应该编写关键字。

  

编译器什么时候不知道何时使函数/方法'内联'?

没有这种情况。编译器无法使内联函数。它所能做的就是内联部分或全部函数调用。如果它没有得到函数的代码就不能这样做(在这种情况下,链接器需要这样做才能这样做)。

  

当一个应用程序为函数/方法写'内联'时是否是多线程的是否重要?

不,这根本不重要。

答案 5 :(得分:5)

  • 编译器什么时候不知道何时使函数/方法'内联'?

这取决于使用的编译器。不要盲目相信现在的编译器比人类更了解如何内联,你不应该出于性能原因使用它,因为它是连接指令而不是优化提示。虽然我同意在意识形态上这些论点正确地遇到现实可能是另一回事。

在阅读了多个线程后,我尝试了好奇内联对代码的影响我正在工作,结果是我为GCC获得了可测量的加速并且没有为英特尔编译器加速。

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

因此,如果您将GCC 4.6限定为现代编译器,结果是如果您编写CPU密集型任务并知道确切的瓶颈位置,则内联指令仍然很重要。

答案 6 :(得分:3)

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

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

答案 7 :(得分:2)

  

默认情况下,gcc在没有编译时不会内联任何函数   已启用优化。我不知道visual studio - deft_code

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

#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获取数据。模板实例化也属于它们自己的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 inline仅影响编译器。这对编译器意味着:仅在转换单元中使用此函数/数据时才发出符号,并作为常规静态符号这样做(不带.globl指令存储在.text /.data中)。对于汇编程序,staticstatic inline

之间没有区别

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

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在命名空间上,请参见thisthis

答案 10 :(得分:0)

Inline 关键字请求编译器将函数调用替换为函数的主体,它首先对表达式求值,然后传递。调用开销,因为不需要存储返回地址,并且函数参数不需要堆栈内存。

何时使用:

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

答案 11 :(得分:-1)

开发和调试代码时,请退出inline。它使调试变得复杂。

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

在算法完成之前,对这种关于性能优化的想法是premature optimization

答案 12 :(得分:-1)

什么时候应该内联:

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

2.函数应该很小,经常调用并且使内联非常有利,因为根据80-20规则,尝试使这些函数内联,这对程序性能有重大影响。

我们知道内联只是一个类似于寄存器的编译器请求,它会花费你的对象代码大小。

答案 13 :(得分:-1)

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

(但请参阅Is there any reason why not to use link time optimization?

答案 14 :(得分:-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;
}

有关更多信息,请参见here