精华内容
下载资源
问答
  • 什么是内联函数

    千次阅读 2018-11-04 22:34:15
    内联函数inline:引入内联函数的目的为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都在运行...

    内联函数inline:引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:1.在内联函数内不允许使用循环语句和开关语句;2.内联函数的定义必须出现在内联函数第一次调用之前;3.类结构中所在的类说明内部定义的函数是内联函数。

    展开全文
  • 什么是内联函数

    千次阅读 2013-06-29 15:24:54
    (1)什么是内联函数? 内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。 (2)为什么要引入内联函数? 当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题。另外,前面我们...

    (1)什么是内联函数?
    内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。

    (2)为什么要引入内联函数?
    当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题。另外,前面我们讲到了宏,里面有这么一个例子:
    #define ABS(x) ((x)>0? (x):-(x))
    当++i出现时,宏就会歪曲我们的意思,换句话说就是:宏的定义很容易产生二意性。
      
    我们可以看到宏有一些难以避免的问题,怎么解决呢?前面我们已经尽力替换了。

    下面我们用内联函数来解决这些问题。

    (3)为什么inline能取代宏?
    1、 inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
    2、 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
    3、 inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。

    (4)内联函数和宏的区别?
    内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,比较方便。

    (5)什么时候用内联函数?
    内联函数在C++类中,应用最广的,应该是用来定义存取函数。我们定义的类中一般会把数据成员定义成私有的或者保护的,这样,外界就不能直接读写我们类成员的数据了。对于私有或者保护成员的读写就必须使用成员接口函数来进行。如果我们把这些读写成
    员函数定义成内联函数的话,将会获得比较好的效率。
    Class A
    {
    Private:
    int nTest;
     Public:
    int readtest() { return nTest;}
    void settest(int I) { nTest=I; }
    }

    (6)如何使用内联函数?
    我们可以用inline来定义内联函数。
    inline int A (int x) { return 2*x; }
    不过,任何在类的说明部分定义的函数都会被自动的认为是内联函数。

    (7)内联函数的优缺点?
    我们可以把它作为一般的函数一样调用,但是由于内联函数在需要的时候,会像宏一样展开,所以执行速度确比一般函数的执行速度要快。当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。(换句话说就是,你使用内联函数,只不过是向编译器提出一个申请,编译器可以拒绝你的申请)这样,内联函数就和普通函数执行效率一样了。

    (8)如何禁止函数进行内联?
    如果使用VC++,可以使用/Ob命令行参数。当然,也可以在程序中使用 #pragma auto_inline达到相同的目的。

    (9)注意事项:
    1.在内联函数内不允许用循环语句和开关语句。
    2.内联函数的定义必须出现在内联函数第一次被调用之前。

    展开全文
  • 什么是内联函数 inline

    2020-03-18 22:36:16
    内联函数(inlinefunction) 要好好努力然后多挣钱,才养得起自己这个爱吃的胃,爱玩儿的心,爱逛爱买的习惯,就算一个人往一个人来,也能生活得很好。 c++从c中继承的一个重要特征就是效率。假如c++的效率明显低于...

    内联函数(inlinefunction)

    在这里插入图片描述

    要好好努力然后多挣钱,才养得起自己这个爱吃的胃,爱玩儿的心,爱逛爱买的习惯,就算是一个人往一个人来,也能生活得很好。

    c++从c中继承的一个重要特征就是效率。假如c++的效率明显低于c的效率,那 么就会有很大的一批程序员不去使用c++了。 在c中我们经常把一些短并且执行 频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数 调用的开销,这些都由预处理来完成。 但是在c++出现之后,使用预处理宏会出 现两个问题: 第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏 一些难以发现的错误。 第二个问题是c++特有的,预处理器不允许访问类的成 员,也就是说预处理器宏不能用作类类的成员函数。 为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里 访问自如,c++引入了内联函数(inlinefunction). 内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那 样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。

    宏函数(带参数的宏)的缺点:

    第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。
    第二个问题是c++特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类的成员函数
    内联函数:内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数
    内联函数:是一个真正的函数。函数的替换 发生在编译阶段

    inline int my_mul(int x,int y)
    {
        return x*y;
    }
    void test01()
    {
        cout<<"my_mul = "<<my_mul(10+10,20+20)<<endl;
    }
    

    任何在类内部定义的函数自动成为内联函数。

    class Person
    { 
    public: 
    	Person()
    	{ 
    		cout << "构造函数!" << endl; 
    	} 
    	void PrintPerson()
    	{ 
    		cout << "输出 Person!" << endl; 
    	} 
    

    内联函数条件:

    1、不能存在任何形式的循环语句
    2、不能存在过多的条件判断语句
    3、函数体不能过于庞大 不能对函数进行取址操作
    内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函 数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会 内联小的、简单的函数。

    展开全文
  • 题目:什么是内联函数

    千次阅读 2015-03-19 11:03:06
    意指:当编译器发现某段代码在调用一个内联函数时,它不是去调用该函数,而是将该函数的代码,整段插入到当前位置。这样做的好处省去了调用的过程,加快程序运行速度。(函数的调用过程,由于有前面所说的参数入栈...

    一、什么叫inline函数? 
    inline(小心,不是online),翻译成“内联”或“内嵌”。意指:当编译器发现某段代码在调用一个内联函数时,它不是去调用该函数,而是将该函数的代码,整段插入到当前位置。这样做的好处是省去了调用的过程,加快程序运行速度。(函数的调用过程,由于有前面所说的参数入栈等操作,所以总要多占用一些时间)。这样做的不好处:由于每当代码调用到内联函数,就需要在调用处直接插入一段该函数的代码,所以程序的体积将增大。拿生活现象比喻,就像电视坏了,通过电话找修理工来,你会嫌慢,于是干脆在家里养了一个修理工。这样当然是快了,不过,修理工住在你家可就要占地儿了。内联函数并不是必须的,它只是为了提高速度而进行的一种修饰。要修饰一个函数为内联型,使用如下格式: 
    inline 函数的声明或定义 
    简单一句话,在函数声明或定义前加一个 inline 修饰符。 
    inline int max(int a, int b) 

       return (a>b)? a : b; 
    }

    内联函数的本质是,节省时间但是消耗空间。

    二、inline函数的规则

    (1)、一个函数可以自已调用自已,称为递归调用(后面讲到),含有递归调用的函数不能设置为inline;

    (2)、使用了复杂流程控制语句:循环语句和switch语句,无法设置为inline;

    (3)、由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过5行。

    (4)、inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。

    (5)、在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。比如下面代码片段: 
    //函数一开始没有被声明为inline: 
    void foo(); 
    //然后就有代码调用它: 
    foo(); 
    //在调用后才有定义函数为inline: 
    inline void foo() 

       ...... 

    代码是的foo()函数最终没有实现inline;

    (6)、为了调试方便,在程序处于调试阶段时,所有内联函数都不被实现。

    三、使用内联函数时应注意以下几个问题:

    (1) 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。 
    (2) 内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数。 
    (3) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。 
    (4) 内联函数要在函数被调用之前声明。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。

    四、扩展

    在C中为什么要使用宏呢?
    因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。
    而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。


    宏和函数的区别:

    1. 宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类型的,可以是各种各样的类型.
    2. 宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的.
    3. 宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的是编译的时间,而函数占用的是执行时的时间.
    4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的.
    5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的.


    宏也有很多的不尽人意的地方。
    1、宏不能访问对象的私有成员。
    2、宏的定义很容易产生二意性。

     


    我们举个例子:

    #define square(x) (x*x)

    我们用一个数字去调用它,square(5),这样看上去没有什么错误,结果返回25,是正确的,但是如果我们用squre (5+5)去调用的话,我们期望的结果是100,而宏的调用结果是(5+5*5+5),结果是35,这显然不是我们要得到的结果。避免这些错误的方法,一是给宏的参数都加上括号。


    #define square(x) ((x)*(x))

    这样可以确保不会出错,但是,即使使用了这种定义,这个宏依然有可能出错,例如使用TABLE_MULTI(a++)调用它,他们本意是希望得到(a+1)*(a+1)的结果,而实际上呢?我们可以看看宏的展开结果: (a++)*(a++),如果a的值是4,我们得到的结果是6*6=36。而我们期望的结果是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 iI+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)当在类的定义体外时,需要在该成员函数的定义前面加“inline”关键字,显式地告诉编译器该函数在调用时需要“内联”处理,如:

    class Student

    {

    public:

            String GetName();

            int     GetAge();

            void        SetAge(int ag);

            ……

    private:

            String  name;

            int     age;

            ……

    };

    inline String GetName()

    {

            return name;

    }

    inline int GetAge()

    {

            return age;

    }

    inline void SetAge(int ag)

    {

            age = ag;

    }

    (2)当在类的定义体内且声明该成员函数时,同时提供该成员函数的实现体。此时,“inline”关键字并不是必需的,如:

    class Student

    {

    public:

            String GetName()       { return name; }

            int     GetAge()        { return age; }

            void        SetAge(int ag) { age = ag; }

            ……

    private:

            String  name;

            int     age;

            ……

    };

    当普通函数(非类成员函数)需要被内联时,则只需要在函数的定义时前面加上“inline”关键字,如:

        inline int DoSomeMagic(int a, int b)

    {

            return a * 13 + b % 4 + 3;

    }

    因为C++是以“编译单元”为单位编译的,而一个编译单元往往大致等于一个“.cpp”文件。在实际编译前,预处理器会将“#include”的各头文件的内容(可能会有递归头文件展开)完整地拷贝到cpp文件对应位置处(另外还会进行宏展开等操作)。预处理器处理后,编译真正开始。一旦C++编译器开始编译,它不会意识到其他cpp文件的存在。因此并不会参考其他cpp文件的内容信息。联想到内联的工作是由编译器完成的,且内联的意思是将被调用内联函数的函数体代码直接代替对该内联函数的调用。这也就意味着,在编译某个编译单元时,如果该编译单元会调用到某个内联函数,那么该内联函数的函数定义(即函数体)必须也包含在该编译单元内。因为编译器使用内联函数体代码替代内联函数调用时,必须知道该内联函数的函数体代码,而且不能通过参考其他编译单元信息来获得这一信息。

    如果有多个编译单元会调用到某同一个内联函数,C++规范要求在这多个编译单元中该内联函数的定义必须是完全一致的,这就是“ODR”(one-definition rule)原则。考虑到代码的可维护性,最好将内联函数的定义放在一个头文件中,用到该内联函数的各个编译单元只需#include该头文件即可。进一步考虑,如果该内联函数是一个类的成员函数,这个头文件正好可以是该成员函数所属类的声明所在的头文件。这样看来,类成员内联函数的两种声明可以看成是几乎一样的,虽然一个是在类外,一个在类内。但是两个都在同一个头文件中,编译器都能在#include该头文件后直接取得内联函数的函数体代码。讨论完如何声明一个内联函数,来查看编译器如何内联的。继续上面的例子,假设有个foo函数:

    #include "student.h"

    ...

    void foo()

    {

            ...

            Student abc;

            abc.SetAge(12);

            cout << abc.GetAge();

            ...

    }

    foo函数进入foo函数时,从其栈帧中开辟了放置abc对象的空间。进入函数体后,首先对该处空间执行Student的默认构造函数构造abc对象。然后将常数12压栈,调用abc的SetAge函数(开辟SetAge函数自己的栈帧,返回时回退销毁此栈帧)。紧跟着执行abc的GetAge函数,并将返回值压栈。最后调用cout的<<操作符操作压栈的结果,即输出。

    内联后大致如下:

    #include "student.h"

    ...

    void foo()

    {

            ...

            Student abc;

            {

                abc.age = 12;

            }

            int tmp = abc.age;

            cout << tmp;

            ...

    }

    这时,函数调用时的参数压栈、栈帧开辟与销毁等操作不再需要,而且在结合这些代码后,编译器能进一步优化为如下结果:

    #include "student.h"

    ...

    void foo()

    {

            ...

            cout << 12;

            ...

    }

    这显然是最好的优化结果;相反,考虑原始版本。如果SetAge/GetAge没有被内联,因为非内联函数一般不会在头文件中定义,这两个函数可能在这个编译单元之外的其他编译单元中定义。即foo函数所在编译单元看不到SetAge/GetAge,不知道函数体代码信息,那么编译器传入12给SetAge,然后用GetAge输出。在这一过程中,编译器不能确信最后GetAge的输出。因为编译这个编译单元时,不知道这两个函数的函数体代码,因而也就不能做出最终版本的优化。

    从上述分析中,可以看到使用内联函数至少有如下两个优点。

    (1)减少因为函数调用引起开销,主要是参数压栈、栈帧开辟与回收,以及寄存器保存与恢复等。

    (2)内联后编译器在处理调用内联函数的函数(如上例中的foo()函数)时,因为可供分析的代码更多,因此它能做的优化更深入彻底。

    第一条优点对于开发人员来说往往更显而易见一些,第二条优点对最终代码的优化可能贡献更大。

    这时,有必要简单介绍函数调用时都需要执行哪些操作,这样可以帮助分析一些函数调用相关的问题。假设下面代码:

    void foo()

    {

            ...

            i = func(a, b, c);                                          ①

            ...                                                         ②

    }

    调用者(这里是foo)在调用前需要执行如下操作。

    (1)参数压栈:这里是a、b和c。压栈时一般都是按照逆序,因此是c->b->c。如果a、b和c有对象,则需要先进行拷贝构造。

    (2)保存返回地址:即函数调用结束返回后接着执行的语句的地址,这里是②处语句的地址。

    (3)保存维护foo函数栈帧信息的寄存器内容:如SP(堆栈指针)和FP(栈帧指针)等。到底保存哪些寄存器与平台相关,但是每个平台肯定都会有对应的寄存器。

    (4)保存一些通用寄存器的内容:因为有些通用寄存器会被所有函数用到,所以在foo调用func之前,这些寄存器可能已经放置了对foo有用的信息。这些寄存器在进入func函数体内执行时可能会被func用到,从而被覆写。因此foo在调用func前保存一份这些通用寄存器的内容,这样在func返回后可以恢复它们。

    接着调用func函数,它首先通过移动栈指针来分配所有在其内部声明的局部变量所需的空间,然后执行其函数体内的代码等。

    最后当func执行完毕,函数返回时,foo函数还需要执行如下善后处理。

    (1)恢复通用寄存器的值。

    (2)恢复保存foo函数栈帧信息的那些寄存器的值。

    (3)通过移动栈指针,销毁func函数的栈帧,

    (4)将保存的返回地址出栈,并赋给IP寄存器。

    (5)通过移动栈指针,回收传给func函数的参数所占用的空间。

    我们知道,如果传入参数和返回值为对象时,还会涉及对象的构造与析构,函数调用的开销就会更大。尤其是当传入对象和返回对象是复杂的大对象时,更是如此。

    因为函数调用的准备与善后工作最终都是由机器指令完成的,假设一个函数之前的准备工作与之后的善后工作的指令所需的空间为SS,执行这些代码所需的时间为TS,现在可以更细致地从空间与时间两个方面来分析内联的效果。

    (1)在空间上,一般印象是不采用内联,被调用函数的代码只有一份,调用它的地方使用call语句引用即可。而采用内联后,该函数的代码在所有调用其处都有一份拷贝,因此最后总的代码大小比采用内联前要大。但事实不总是这样的,如果一个函数a的体代码大小为AS,假设a函数在整个程序中被调用了n次,不采用内联时,对a的调用只有准备工作与善后工作两处会增加最后的代码量开销,即a函数相关的代码大小为:n * SS + AS。采用内联后,在各处调用点都需要将其函数体代码展开,即a函数相关的代码大小为n * AS。这样比较二者的大小,即比较(n * SS + AS)与(n*AS)的大小。考虑到n一般次数很多时,可以简化成比较SS与AS的大小。这样可以得出大致结论,如果被内联函数自己的函数体代码量比因为函数调用的准备与善后工作引入的代码量大,内联后程序的代码量会变大;相反,当被内联函数的函数体代码量比因为函数调用的准备与善后工作引入的代码量小,内联后程序的代码量会变小。这里还没有考虑内联的后续情况,即编译器可能因为获得的信息更多,从而对调用函数的优化做得更深入和彻底,致使最终的代码量变得更小。

    (2)在时间上,一般而言,每处调用都不再需要做函数调用的准备与善后工作。另外内联后,编译器在做优化时,看到的是调用函数与被调用函数连成的一大块代码。即获得的代码信息更多,此时它对调用函数的优化可以做得更好。最后还有一个很重要的因素,即内联后调用函数体内需要执行的代码是相邻的,其执行的代码都在同一个页面或连续的页面中。如果没有内联,执行到被调用函数时,需要跳到包含被调用函数的内存页面中执行,而被调用函数所属的页面极有可能当时不在物理内存中。这意味着,内联后可以降低“缺页”的几率,知道减少“缺页”次数的效果远比减少一些代码量执行的效果。另外即使被调用函数所在页面可能也在内存中,但是因为与调用函数在空间上相隔甚远,所以可能会引起“cache miss”,从而降低执行速度。因此总的来说,内联后程序的执行时间会比没有内联要少。即程序的速度更快,这也是因为内联后代码的空间“locality”特性提高了。但正如上面分析空间影响时提到的,当AS远大于SS,且n非常大时,最终程序的大小会比没有内联时要大很多。代码量大意味着用来存放代码的内存页也会更多,这样因为执行代码而引起的“缺页”也会相应增多。如果这样,最终程序的执行时间可能会因为大量的“缺页”而变得更多,即程序的速度变慢。这也是为什么很多编译器对于函数体代码很多的函数,会拒绝对其进行内联的请求。即忽略“inline”关键字,而对如同普通函数那样编译。

    综合上面的分析,在采用内联时需要内联函数的特征。比如该函数自己的函数体代码量,以及程序执行时可能被调用的次数等。当然,判断内联效果的最终和最有效的方法还是对程序的大小和执行时间进行实际测量,然后根据测量结果来决定是否应该采用内联,以及对哪些函数进行内联。

    如下根据内联的本质来讨论与其相关的一些其他特点。

    如前所述,因为调用内联函数的编译单元必须有内联函数的函数体代码信息。又因为ODR规则和考虑到代码的可维护性,所以一般将内联函数的定义放在一个头文件中,然后在每个调用该内联函数的编译单元中#include该头文件。现在考虑这种情况,即在一个大型程序中,某个内联函数因为非常通用,而被大多数编译单元用到对该内联函数的一个修改,就会引起所有用到它的编译单元的重新编译。对于一个真正的大型程序,重新编译大部分编译单元往往意味着大量的编译时间。因此内联最好在开发的后期引入,以避免可能不必要的大量编译时间的浪费。

    再考虑这种情况,如果某开发小组在开发中用到了第三方提供的程序库,而这些程序库中包含一些内联函数。因为该开发小组的代码中在用到第三方提供的内联函数处,都是将该内联函数的函数体代码拷贝到调用处,即该开发小组的代码中包含了第三方提供代码的“实现”。假设这个第三方单位在下一个版本中修改了某些内联函数的定义,那么虽然这个第三方单位并没有修改任何函数的对外接口,而只是修改了实现,该开发小组要想利用这个新的版本,仍然需要重新编译。考虑到可能该开发小组的程序已经发布,那么这种重新编译的成本会相当高;相反,如果没有内联,并且仍然只是修改实现,那么该开发小组不必重新编译即可利用新的版本。

    因为内联的本质就是用函数体代码代替对该函数的调用,所以考虑递归函数,如:

    [inline] int foo(int n)

    {

            ...

            return foo(n-1);

    }

    如果编译器编译某个调用此函数的编译单元,如:

    void func()

    {

            ...

            int m = foo(n);

            ...

    }

    考虑如下两种情况。

    (1)如果在编译该编译单元且调用foo时,提供的参数n不能知道其实际值,则编译器无法知道对foo函数体进行多少次代替。在这种情况下,编译器会拒绝对foo函数进行内联。

    (2)如果在编译该编译单元且调用foo时,提供的参数n能够知道其实际值,则编译器可能会视n值的大小来决定是否对foo函数进行内联。因为如果n很大,内联展开可能会使最终程序的大小变得很大。

    如前所述,因为内联函数是编译期行为,而虚拟函数是执行期行为,因此编译器一般会拒绝对虚拟函数进行内联的请求。但是事情总有例外,内联函数的本质是编译器编译调用某函数时,将其函数体代码代替call调用,即内联的条件是编译器能够知道该处函数调用的函数体。而虚拟函数不能够被内联,也是因为在编译时一般来说编译器无法知道该虚拟函数到底是哪一个版本,即无法确定其函数体。但是在两种情况下,编译器是能够知道虚拟函数调用的真实版本的,因此虚拟函数可以被内联。

    其一是通过对象,而不是指向对象的指针或者对象的引用调用虚拟函数,这时编译器在编译期就已经知道对象的确切类型。因此会直接调用确定的某虚拟函数实现版本,而不会产生“动态绑定”行为的代码。

    其二是虽然是通过对象指针或者对象引用调用虚拟函数,但是编译时编译器能知道该指针或引用对应到的对象的确切类型。比如在产生的新对象时做的指针赋值或引用初始化,发生在于通过该指针或引用调用虚拟函数同一个编译单元并且二者之间该指针没有被改变赋值使其指向到其他不能确切知道类型的对象(因为引用不能修改绑定,因此无此之虞)。此时编译器也不会产生动态绑定的代码,而是直接调用该确定类型的虚拟函数实现版本。

    在这两种情况下,编译器能够将此虚拟函数内联化,如:

    inline virtual int x::y (char* a)

    {

        ...

    }

    void z (char* b)

    {

        x_base* x_pointer = new x(some_arguments_maybe);

        x x_instance(maybe_some_more_arguments);

        x_pointer->y(b);

        x_instance.y(b);

    当然在实际开发中,通过这两种方式调用虚拟函数时应该非常少,因为虚拟函数的语义是“通过基类指针或引用调用,到真正运行时才决定调用哪个版本”。

    从上面的分析中已经看到,编译器并不总是尊重“inline”关键字。即使某个函数用“inline”关键字修饰,并不能够保证该函数在编译时真正被内联处理。因此与register关键字性质类似,inline仅仅是给编译器的一个“建议”,编译器完全可以视实际情况而忽略之。

    另外从内联,即用函数体代码替代对该函数的调用这一本质看,它与C语言中的函数宏(macro)极其相似,但是它们之间也有本质的区别。即内联是编译期行为,宏是预处理期行为,其替代展开由预处理器来做。也就是说编译器看不到宏,更不可能处理宏。另外宏的参数在其宏体内出现两次或两次以上时经常会产生副作用,尤其是当在宏体内对参数进行++或--操作时,而内联不会。还有,预处理器不会也不能对宏的参数进行类型检查。而内联因为是编译器处理的,因此会对内联函数的参数进行类型检查,这对于写出正确且鲁棒的程序,是一个很大的优势。最后,宏肯定会被展开,而用inline关键字修饰的函数不一定会被内联展开。

    最后顺带提及,一个程序的惟一入口main()函数肯定不会被内联化。另外,编译器合成的默认构造函数、拷贝构造函数、析构函数,以及赋值运算符一般都会被内联化


    展开全文
  • C++ 中什么是内联函数

    2010-04-25 17:11:00
    引入内联函数的目的为了解决程序中函数调用的效率问题。 函数一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序...
  • 什么是内联函数: 内联函数是一种C++增强功能,可以增加程序的运行时间。 可以向编译器指示某些函数,使它们内联,以便编译器可以在调用它们的任何地方,替换那些函数定义。 编译器在编译时,替换内联函数的定义,而...
  • 引入内联函数的目的为了解决程序中函数调用的效率问题。 函数一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序...
  • 什么是内联函数? 什么时候使用内联函数? 内联函数和常规函数的区别 如何使用内联函数? 注意: 代码示例 运行结果: 内联与宏有什么区别 什么是内联函数? 内联函数是C++为了提高程序运算速度所做的一项...
  • 内联函数

    万次阅读 多人点赞 2018-08-03 18:07:48
    1.什么是内联函数 用关键字inline修饰的函数就是内联函数。关键字在函数声明和定义的时候都要加上,不写系统还是会当成常规函数   2.内联函数与一般函数的区别 1)内联含函数比一般函数在前面多一个inline修饰...
  • (1)什么是内联函数?内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。(2)为什么要引入内联函数?当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题。另外,前面我们讲到了宏,...
  • inline内联函数

    2021-01-11 17:49:18
    1.什么是内联函数 用关键字inline修饰的函数就是内联函数。关键字在函数声明和定义的时候都要加上,不写系统还是会当成常规函数 2.内联函数与一般函数的区别 1)内联含函数比一般函数在前面多一个inline修饰符 2)...
  • 1, 在c语言中内联函数什么作用1.内联函数可减少cpu的系统开销,并且程序的整体速度将加快,但当内联函数很大时,会有相反的作用,因此一般比较小的函数才使用内联函数.2.有两种内联函数的声明方法,一种在函数前...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,500
精华内容 600
关键字:

什么是内联函数