C++杂谈——inline

概述

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

什么是内联函数

内联函数是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段。

值得注意的是,内联函数仅仅是对编译器的内联建议,编译器是否觉得采取你的建议取决于函数是否符合内联的有利条件。如果函数体非常大,那么编译器将忽略函数的内联声明,而将内联函数作为普通函数处理。

为什么要使用内联函数

对于发生一个普通的函数调用时会发生什么?

  1. 保存当前程序执行状态。
  2. 将调用函数的函数地址和参数,局部变量压栈。
  3. 将调用函数加载到内存,此时涉及参数的拷贝和跳转到函数执行的内存位置。
  4. 执行调用函数代码,存储函数返回值。
  5. 获取之前函数的地址,返回到原来函数的执行位置,继续运行。

可以看出发生函数的调用时,代价是很高的,这也正是内联函数的优势:通过inline声明,编译器首先在函数调用处使用函数体本身语句替换了函数调用语句,然后编译替换后的代码。因此,通过内联函数,编译器不需要跳转到内存其他地址去执行函数调用,也不需要保留函数调用时的现场数据。

比如说这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>    
//函数定义为inline即:内联函数
inline char* dbtest(int a)
{
return (i % 2 > 0) ? "奇" : "偶";
}
int main()
{
int i = 0;
for (i=1; i < 100; i++)
{
printf("i:%d 奇偶性:%s /n", i, dbtest(i));
}
}

如果没有将dbtest声明为inline,则在程序运行时,将发生100次函数的压栈,出栈,拷贝,加载等调用工作(如果编译器没有进行优化),开销不容忽视。如果声明为inline,其实在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i%2>0)?”奇”:”偶”,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”,如果在函数的声明处加inline关键字,但是在函数定义时没有加,那么该函数仍然不是内联函数;不管在函数声明处有没有加inline关键字,只要在函数的定义出加inline,那么这个函数就是内联的。不过个人认为好的习惯应该是在声明和定义位置都加,因为程序不仅要让编译器看懂,还要让别的程序员看懂。

内联函数的优缺点

既然内联函数可以提升程序的运行效率,那为什么不将所有的函数都定义为内联函数呢?
可以说内联函数的效率提升是以牺牲空间为代价的,典型的空间换时间策略。如果函数结构过于复杂,或者篇幅过长(google标准建议超过10行就算过长),那么这样的替换是得不偿失的(即使定义为inline,编译器也不会为之所动)。

通过下面这些优缺点总结大概会更理解为什么要使用inline函数:

优点:

  1. 它通过避免函数调用所带来的开销来提高你程序的运行速度。
  2. 当函数调用发生时,它节省了变量弹栈、压栈的开销。
  3. 它避免了一个函数执行完返回原现场的开销。
  4. 通过将函数声明为内联,你可以把函数定义放在头文件内。

缺点:

  1. 因为代码的扩展,内联函数增大了可执行程序的体积。
  2. C++内联函数的展开是中编译阶段,这就意味着如果你的内联函数发生了改动,那么就需要重新编译代码。
  3. 当你把内联函数放在头文件中时,它将会使你的头文件信息变多,不过头文件的使用者不用在意这些。
  4. 有时候内联函数并不受到青睐,比如在嵌入式系统中,嵌入式系统的存储约束可能不允许体积很大的可执行程序。

宏与内联函数

宏和普通函数调用的区别

  1. 宏只做简单的字符串替换,函数是参数传递,所以必然有参数类型检查(支持各种类型,而不是只有字符串)。
  2. 宏不经计算而直接替换参数,函数调用则是将参数表达式求值再传递给形参。
  3. 宏在编译前进行,即先替换再编译。而函数是编译后,在执行时才调用的。宏占编译时间,而函数占执行时间。
  4. 宏参数不占空间,因为只做字符串替换,而函数调用时参数传递是变量之间的传递,形参作为局部变量占内存空间。
  5. 函数调用需要保留现场,然后转入调用函数执行,执行完毕再返回主调函数,这些耗费在宏中是没有的。

使用宏和内联函数都可以节省在函数调用方面的时间和空间开销。二者都是为了提高效率,但是却有着显著的区别:

  1. 在使用时,宏只做简单的预处理器符号表(字符串)中的简单替换。而内联函数可以进行参数类型检查,且具有返回值(也能被强制转换为可转换的合适类型)。
  2. 内联函数首先是函数,函数的许多性质都适用于内联函数(如内联函数可以重载)。
  3. 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了(无法将this指针放在合适位置)。

下面是《高质量C++代码》中的一段话:


参考资料:
《深度探索C++对象模型》
内联函数 —— C 中关键字 inline 用法解析
c++ 内联函数(一看就懂)
C++内联函数
宏与内联(inline)的区别(转载)