内联函数

在C++语言的设计中,内联函数的引入可以说完全是为了性能的考虑。因此在编写对性能要求比较高的C++程序时,非常有必要仔细考量内联函数的使用。 所谓"内联",即将被调用函数的函数体代码直接地整个插入到该函数被调用处,而不是通过call语句进行。当然,编译器在真正进行"内联"时,因为考虑到被内联函数的传入参数、自己的局部变量,以及返回值的因素,不仅仅只是进行简单的代码拷贝,还需要做很多细致的工作,但大致思路如此。

开发人员可以有两种方式告诉编译器需要内联哪些类成员函数,一种是在类的定义体外;一种是在类的定义体内。

(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()函数)时,因为可供分析的代码更多,因此它能做的优化更深入彻底。前一条优点对于开发人员来说往往更显而易见一些,但往往这条优点对最终代码的优化可能贡献更大。

C++ 内联函数 摘自 C++ 应用程序性能优化的更多相关文章

  1. 内联函数 inline

    (一)inline函数(摘自C++ Primer的第三版) 在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联. inline int min(int first, int ...

  2. c++内联函数与静态函数

    不能是虚函数的成员函数有:静态成员函数,内联成员函数,构造函数.没有什么函数需要硬性规定为虚函数,一般析构函数会被定义为虚函数其他就是在继承类中可能需要override的类成员函数应该定义为虚函数 h ...

  3. 你好,C++(28)用空间换时间 5.2 内联函数 5.3 重载函数

    5.2  内联函数 通过5.1节的学习我们知道,系统为了实现函数调用会做很多额外的幕后工作:保存现场.对参数进行赋值.恢复现场等等.如果函数在程序内被多次调用,且其本身比较短小,可以很快执行完毕,那么 ...

  4. 内联函数(Inline Functions)

    影响性能的一个重要因素是内联技巧.内联函数也可称为内嵌函数. 在C++中,函数调用需要建立栈环境,进行参数复制,保护调用现场,返回时,还要进行返回值复制,恢复调用现场.这些工作都是与完成特定任务的操作 ...

  5. c++ 内联函数 (讲解的TM真好)

    1.  内联函数 在C++中我们通常定义以下函数来求两个整数的最大值: 复制代码 代码如下: int max(int a, int b) {  return a > b ? a : b; } 为 ...

  6. C++ 内联函数inline

    http://blog.csdn.net/u011327981/article/details/50601800 1.  内联函数 在C++中我们通常定义以下函数来求两个整数的最大值: 复制代码 代码 ...

  7. C++内联函数

    在C语言中,我们使用宏定义函数这种借助编译器的优化技术来减少程序的执行时间,那么在C++中有没有相同的技术或者更好的实现方法呢?答案是有的,那就是内联函数.内联函数作为编译器优化手段的一种技术,在降低 ...

  8. C++ 内联函数笔记

    要使用内联函数,必须采取下述措施之一: +在函数声明前加上关键字inline: +在函数定义前加上关键字inline. 通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方 ...

  9. 我的c++学习(6)默认参数和内联函数

    默认参数 一般情况下,函数调用时实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有默认参数的函数,这种函数调用时实参个数可以与形参不相同.“默认参数”指在定义或声明函数时为形参指定默认 ...

随机推荐

  1. 如何使用delphi将Clientdataset的Delta保存到数据库中

    [delphi] view plain copy //ATableName-表名, AKeyField-主键,多个主键用;隔开,如 ;pid;times; from:unit HlsImplBase; ...

  2. 哈希算法MD5和SHA1的C#实现

    /* * 哈希算法MD5和SHA1的C#实现 *  *  * 夏春涛 Email:xChuntao@163.com  * Blog:http://bluesky521.cnblogs.com * 运行 ...

  3. 这可能是你少有的能get到测试用例编写精髓的机会!

    自动化测试用例的编写是实现项目自动化的核心,合理的用例设计是保证自动化效益和实用性的关键,也直接决定了自动化脚本是否具备可扩展和可维护性.由此,本篇文章主要为大家介绍了测试用例编写的规范和注意事项. ...

  4. Unreal Engine* 4/英特尔® VTune™ Amplifier 使用指南

    借助英特尔 VTune Amplifier,可以通过单一易用的分析界面获得先进的分析功能.UE4 和英特尔 VTune Amplifier 相互配合,支持调查代码并进行分析,从而在多个内核上顺畅运行. ...

  5. PHP学习(2)——操作符与迭代整理

    目录: 10.操作符整理 11.表单计算代码 12.优先级与结合性 13.可变函数 14.条件判断 15.循环迭代 16.跳出控制 17.可替换的控制结构 10.操作符 10.1 算术操作符 算术操作 ...

  6. Hbase和Hadoop的内存参数调优 + 前端控制台

    1.hadoop的内存配置调优 mapred-site.xml的内存调整 <property> <name>mapreduce.map.memory.mb</name&g ...

  7. IT架构的本质

    老僧三十年前未参禅时,见山是山,见水是水. 及至后来,亲见知识,有个入出,见山不是山,见水不是水. 而今得个休歇处,依前见山只是山,见水只是水. 参禅的三重境界在IT技术圈同样适用,初学者感叹每个产品 ...

  8. Oracle - WITH AS -用于查询当月与上月数据

    注:在之前工作的时候,数据需要根据时间查询出当月值和上月的值. 语法: WITH A AS( SELECT * FROM TABLE ), B AS (SELECT * FROM TABLE)SELE ...

  9. 使用SecureCRT连接虚拟机中Linux系统 和 虚拟机网络配置

    使用SecureCRT连接步骤:1.首先打开虚拟机,点击左上角的编辑,再点击虚拟网络编辑器(已经进行虚拟网络编辑的忽略此步骤,直接进行第二步) 点击VMnet8网络,点击更改设置,此步骤需要管理员权限 ...

  10. 什么是数据管理DMS

    数据管理(Data Management)支持MySQL.SQL Server.PostgreSQL.PPAS.Petadata等关系型数据库,DRDS等OLTP数据库,ADS.DLA等OLAP数据库 ...