分享

内联函数:static inline 和 extern inline 的含义

 黄南山 2017-07-31

前置简短概述

引入内联函数的目的是为了解决程序中函数调用的效率问题。 

函数是一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序代码和数据的共享。但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题。 

在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时将函数休中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。

1.内联函数可减少cpu的系统开销,并且程序的整体速度将加快,但当内联函数很大时,会有相反的作用,因此一般比较小的函数才使用内联函数.
2.有两种内联函数的声明方法,一种是在函数前使用inline关见字,另一种是在类的内部定义函数的代码,这样的函数将自动转换为内联函数,而且没必要将inline放在函数前面.
3.内联是一种对编译器的请求,下面这些情况会阻止编译器服从这项请求.
如果函数中包含有循环,switch或goto语句,递归函数,含有static的函数.

由此可以看出,内联函数和成员函数没什么区别,区别就在于怎样加快函数的执行速度而已。

内联函数是浪费空间来节省时间的设置,因为函数的调用是很浪费时间的,写成内联函数可以在每次调用时用函数体内容代替函数调用,有点类似一个宏定义。当函数体语句较少,且没有复杂的循环语句,且调用次数较多时,就可以用内联函数。 

 

问:

首先,关于inline就够烦人了,有的书上说inline关键字要加在定义前,声明时可以省略,有的说声明时加上inline函数就变成内联型,有的说声明和定义形式要保持一致。在一个类中声明一个函数,函数的实现在外部,无论是仅仅在内部声明处加inline,还是在外部实现处加inline,或是两个地方都加,编译均能通过,而且也无法通过调试的办法看出对程序到底有啥影响。搞不清到底要怎么写这个inline才比较好,不过可以肯定的是,inline函数的定义部分要放在头文件里,声明和定义分开放会编译出错。 

而且inline还可以和extern关键字、static关键字合用,在网上搜了一下,linux之父linus说过 "static inline" means "we have to have this function, if you use it, but don't inline it, then make a static version of it in this compilation unit". "extern inline" means "I actually _have_ an extern for this function, but if you want to inline it, here's the inline-version". 
这话说的云里雾里的,谁能解释一下,说说你对static inline 和 extern inline用法的理解。

答:

extern inline表示该函数是已声明过的了.由于函数本身可以声明多次,所以extern对函数的影响仅仅把函数的隐藏属性显式化了. 
extern 对于非函数的对象是有用的,因为对象声明时会带来内存的分配,而用 extern就表示该对象已经声明过了,不用再分配内存. 
static是以前C的用法.目的是让该关键字标识的函数只在本地文件可见,同一个程序的其它文件是不可见该函数的.换句话说,就算你其它文件里包含了同名同参数表的函数定义的话,也是不会引起函数重复定义的错误的.因为static是仅在当前文件可见. 

关于inline函数,你说的大部分的都 是对的.我来给你总结一下吧. 
inline函数仅仅是一个建议,对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为你的函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,你声明内联只是一个建议而已. 
其次,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然,就成了非内联函数的调用了.所以,这要求你的每个调用了内联函数的文件都出现了该内联函数的定义,因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦.而你所以声明跟定义要一致,其实是指,如果你在每个文件里都实现一次该内联函数的话,那么,你最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定.所以,最好将内联函数定义放在头文件中. 
而类中的成员函数缺省都是内联的,如果你在类定义时就在类内给出函数,那当然最好.如果你在类中未给出成员函数定义,而你又想内联该函数的话,那在类外要加上inline,否则就认为不是内联的.而且刚说了,内联函数最好放在头文件内,所以最好在类定义的头文件里把类的内联函数都实现了. 
而你说的将声明与实现分开,其实是不会编译出错的,反正我写那么多程序都没试过.将声明与定义分开的话,这样的后果会带来编译器并不随处可见该函数定义,所以,只能在你实现定义的那个文件里,将该函数看成内联(如果可以内联的话),在其它文件,仍看成是普通函数. 
看到这里,我想你应该明白了.那么声明时加inline,实现时要不要加inline呢?呵呵,留给 lz 思考吧. 
  

呵呵,你看的还是英文版的哦.你理解的不是这样,放在头文件中不是只存在一个定义,而是只要包含了该头文件的程序文本文件都存在了这个定义.而inline函数是可以被重复定义的,在C++中,常量对象跟内联函数都是可以多次定义的. 
你要把函数那章都看完就会明白. 
我先假设你的函数符合内联的条件. 
在声明是加inline,定义时不加,则要求编译器编译时,能看到inline的声明,而且在展开点看到该定义,这样,就将其视为内联函数. 
如果你声明没有inline,却在定义时inline了.这时,如果其它要调用该函数的文件看到了它的声明,就认为该函数不是内联的,所以,到了调用处,转到该函数实现的地方,却意外地看到了inline声明,这时,会导致链接出错.若要改正的话,就要让调用该函数的文件也看到有inline的定义,而不是在调用时才看到.你可以在每个文件都加上有inline的定义.(如果不加inline,则会出现重复定义的错误,因为内联函数才可以被重复定义).或者另一种修改方法,你将定义时的inline去掉,这样就成为普通函数,链接不会出错.如果是前一种改法,仍是内联的,因为符合了看到了inline且随处可见其定义的条件. 
如果你将声明跟定义都放在同一个头文件,而在声明时不内联,在实现时内联,这样编译器也是将该函数内联(符合两个条件,看到inline的声明(虽然是在定义时),随处可见其定义). 
总结说来,只要编译器看到有inline出现,而且定义随处可见,就能将函数内联(上边已假设你的函数足够简单可以内联),而不必管是定义还是声明加inline的问题. 
所以,为了方便,将内联函数直接声明时就定义,放在头文件中.这样其它文件包含了该头文件,就在每个文件都出现了内联函数的定义.就可以内联了. 
类的成员函数也一样.只不过,类的成员函数缺省都是内联的,前提是你要在类定义时提供成员函数定义.如果在类定义时不提供函数定义,则要在类外边加上inline,否则将视为普通函数. 
关于这些,你看了C++primer有关函数那章自然会明白的. 

 

 

附:内联函数作用及注意事项

定义

  内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是否能形成内联函数,需要看编译器对该函数定义的具体处理。

动机

  内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。 一个小内存空间的函数非常受益。   如果没有内联函数,编译器可以决定哪些函数内联 。 程序员很少或没有控制哪些职能是内联的,哪些不是。 给这种控制程度,作用是程序员可以选择内联的特定应用 。

函数内联问题

  除了 ​​相关的问题, 内联扩展一般,语言功能作为一个内联函数可能不被视为有价值的,因为它们出现的原因,对于一个数字:   通常,一个编译器是在一个比人类更有利的地位来决定某一特定功能是否应该被内联。 有时,编译器可能无法尽可能多的功能内嵌作为程序员表示。   一个重要的一点需要注意的是代码(内联函数)得到暴露其客户端(调用函数)。   随着功能的演变,它们有可能成为合适的内联,他们不前,或不再在他们面前的内联合适。 而内联或取消内联函数比从宏转换为更容易,但仍需要额外的维修,一般产量相对较少的利益。   用于本机C型编译系统的扩散可以增加编译时间,因为他们的身体的中间表示是到每个调用点,他们都是内联复制内联函数。在代码大小可能增加是由在编译时间可能增加镜像。   C99中内嵌的规范要求只有一个额外在另一个编译单元,功能的外部定义时,相应的内联定义,可以发生在不同的编译单元多次,如果该函数用于地方。这很容易导致连接器,因为这样的定义不是由程序员提供的错误。 出于这个原因,往往是在C99内联一起使用静态的,也给出了函数的内部联系。   在C + +,有必要定义一个在每一个模块(编译单元)内联函数使用一个普通的功能,而必须在只有一个模块中定义它。否则,就不可能编制的所有其他模块一个模块独立。   对于功能问题与优化本身,而不是语言,请参阅使用内联扩展问题 。

行情

  “一个函数声明[。。。]说明符声明一个内联与内联函数。内联说明符指示的实现,内联函数体替代了在调用点是首选通常的函数调用机制。一个实现不要求在调用执行此点内联替代,但是,即使这个内嵌替代省略,由7.1.2内联函数定义的其他规则,仍应得到尊重“。   - 国际标准化组织14882:1998(E)的,目前的C + +标准,第7.1.2   “的函数说明符声明的内联函数是一个内联函数。[。。。]制作一个内联函数的函数表明该函数被调用尽可能快。在何种程度上这些建议是有效的,是实现定义( 注:例如,一个实施内联替换可能不会执行,或者可能只执行替换内联在声明中要求的范围内联的)。   “[。。。]内联定义不提供外部定义的功能,并且不禁止的定义,还有一个是外部的翻译单位。一个内联定义提供了任何其他的外部定义,翻译可能用来实现呼吁在相同的翻译单元的功能。没有指定是否调用该函数内联定义或使用外部定义。“   - 国际标准化组织9899:1999(E)的C99标准,第6.7.4

宏比较

  内联函数的功能和预处理宏的功能相似。相信大家都用过预处理宏,我们会经常定义一些宏,如   #define TABLE_COMP(x) ((x)>0?(x):0)   就定义了一个宏。   为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数   所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行   该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地   址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一   定的时间和空间方面的开销,于是将影响其效率。而宏只是在预处理的地方把   代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个   函数更有效率。   但是宏也有很多的不尽人意的地方。   1、.宏不能访问对象的私有成员。   2、.宏的定义很容易产生二意性。   我们举个例子:   #define TABLE_MULTI(x) (x*x)   我们用一个数字去调用它,TABLE_MULTI(10),这样看上去没有什么错误,   结果返回100,是正确的,但是如果我们用TABLE_MULTI(10+10)去调用的话,   我们期望的结果是400,而宏的调用结果是(10+10*10+10),结果是120,这显   然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。   #define TABLE_MULTI(x) ((x)*(x))   这样可以确保不会出错,但是,即使使用了这种定义,这个宏依然有可能   出错,例如使用TABLE_MULTI(a++)调用它,他们本意是希望得到(a+1)*(a+1)的   结果,而实际上呢?我们可以看看宏的展开结果: (a++)*(a++),如果a的值是   4,我们得到的结果是4*4 = 16,a = 6。而我们期望的结果是5*5=25,这又出现了问题。   事实上,在一些C的库函数中也有这些问题。例如:Toupper(*pChar++)就会对   pChar执行两次++操作,因为Toupper实际上也是一个宏。   我们可以看到宏有一些难以避免的问题,怎么解决呢?   下面就是用我要介绍的内联函数来解决这些问题,我们可以使用内联函数   来取代宏的定义。而且事实上我们可以用内联函数完全取代预处理宏。   内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是   通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时   候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开   销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一   些问题。   我们可以用Inline来定义内联函数,不过,任何在类的说明部分定义的函   数都会被自动的认为是内联函数。   下面我们来介绍一下内联函数的用法。   内联函数必须是和函数体申明在一起,才有效。像这样的申明   Inline Tablefunction(int I)是没有效果的,编译器只是把函数作为普通的函   数申明,我们必须定义函数体。   Inline tablefunction(int I) {return I*I};   这样我们才算定义了一个内联函数。我们可以把它作为一般的函数一样调   用。但是执行速度确比一般函数的执行速度要快。   我们也可以将定义在类的外部的函数定义为内联函数,比如:   Class TableClass{   Private:   Int I,j;   Public:   Int add() { return I+j;};   Inline int dec() { return I-j;}   Int GetNum();   }   inline int tableclass::GetNum(){   return I;   }   上面申明的三个函数都是内联函数。在C++中,在类的内部定义了函数体的   函数,被默认为是内联函数。而不管你是否有inline关键字。   内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的   类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我   们类成员的数据了。   对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把   这些读写成员函数定义成内联函数的话,将会获得比较好的效率。   Class sample{   Private:   Int nTest;   Public:   Int readtest(){ return nTest;}   Void settest(int I) {nTest=I;}   }   当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如   果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式   调用函数。这样,内联函数就和普通函数执行效率一样了。

注意事项

  使用内联函数应注意的事项   内联函数具有一般函数的特性,它与一般函数所不同之处只在于函数调用的处理。一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应注意如下几点: 1.在内联函数内不允许用循环语句和开关语句。 如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。 2.内联函数的定义必须出现在内联函数第一次被调用之前。 3.本栏目讲到的类结构中所有在类说明内部定义的函数是内联函数。   

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多