C++内联函数

Posted 呆呆象呆呆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++内联函数相关的知识,希望对你有一定的参考价值。

C++ 内联函数

背景介绍

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。

一个 C/C++程序的执行过程可以认为是多个函数之间的相互调用过程,它们形成了一个或简单或复杂的调用链条,这个链条的起点是main(),终点也是main()。当main()调用完了所有的函数,它会返回一个值(例如return 0;)来结束自己的生命,从而结束整个程序。

函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。

为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数Inline Function),又称内嵌函数或者内置函数

定义和概念

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。

在类定义中的定义的函数都是内联函数,即使没有使用inline说明符。

举例说明

#include <iostream>
using namespace std;
//内联函数,交换两个数的值
inline void swap(int *a, int *b)
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;

int main()
    int m, n;
    cin>>m>>n;
    cout<<m<<", "<<n<<endl;
    swap(&m, &n);
    cout<<m<<", "<<n<<endl;
    return 0;

运行结果:

45 99
45, 99
99, 45

当编译器遇到函数调用swap(&m, &n)时,会用 swap() 函数的代码替换swap(&m, &n),同时用实参代替形参。这样,程序第 16 行就被置换成:

int temp;
temp = *(&m);
*(&m) = *(&n);
*(&n) = temp;

编译器可能会将 *(&m)*(&n) 分别优化为mn

强调注意事项

关键字inline必须与函数定义体放在一起才能使函数成为内联。在函数声明处添加inline关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的inline关键字。

如下风格的函数Foo 不能成为内联函数:

inline void Foo(int x, int y); // inline 仅与函数声明放在一起
void Foo(int x, int y)


而如下风格的函数Foo 则成为内联函数:

void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起


所以说,inline是一种 “用于实现的关键字” ,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但inline不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

优缺点

优点

内联通过避免函数调用所带来的开销来提高你程序的运行速度。

当函数调用发生时,它节省了变量弹栈、压栈的开销。

避免了一个函数执行完返回原现场的开销。

通过将函数声明为内联,你可以把函数定义放在头文件内。

缺点

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。

另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大。

C++内联函数的展开是中编译阶段,这就意味着如果你的内联函数发生了改动,那么就需要重新编译代码。

当你把内联函数放在头文件中时,它将会使你的头文件信息变多,不过头文件的使用者不用在意这些。

有时候内联函数并不受到青睐,比如在嵌入式系统中,嵌入式系统的存储约束可能不允许体积很大的可执行程序。

所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。

使用建议

使用:

  • 当对程序执行性能有要求时,那么就适当使用内联函数
  • 当你想宏定义一个函数时,使用内联函数
  • 写一些功能专一且性能关键的函数,这些函数的函数体不大,包含了很少的执行语句。通过inline声明,编译器不需要跳转到内存其他地址去执行函数调用,也不需要保留函数调用时的现场数据。
  • 在类内部定义的函数会默认声明为inline函数,这有利于 类实现细节的隐藏。(但也需要斟酌如果不需要隐藏的时候,其实大部分是不推荐默认inline的)

不使用:

  • 不使用:如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
  • 不使用:如果函数体内出现循环或者开关语句;那么执行函数体内代码的时间要比函数调用的开销大。

inline与编译器,头文件

对函数作inline声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为inline编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。具体是否会被编译器优化为内联也要看优化级别。

有些函数即使声明为内联的也不一定会被编译器内联,这点很重要。比如虚函数和递归函数就不会被正常内联。通常,递归函数不应该声明成内联函数。(递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。虚函数内联的主要原因则是想把它的函数体放在类定义内,为了图个方便,抑或是当作文档描述其行为, 比如精短的存取函数。

将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。

参考文献

C++ 内联函数 | 菜鸟教程

现在的编译器的inline策略是怎样的? - 知乎

C++ inline内联函数详解

内联函数 —— C 中关键字 inline 用法解析_zqixiao_09的博客-CSDN博客_c语言 inline

C++内联函数 - melonstreet - 博客园

以上是关于C++内联函数的主要内容,如果未能解决你的问题,请参考以下文章

C++内联函数

C++ inline内联函数详解

C++中内联函数的用法

C++中,int&&表示啥意思?和int&有啥不同?

关于C++内联函数

C++ 为啥要引入内联函数、、