内联函数inline

在机器层面,调用函数之前可能需要预先执行一些指令。调用本身需要跳转到函数的第一条指令,函数本身可能也需要执行一些格外的指令来启动执行。如果函数有参数,参数需要被复制(因为C通过值传递参数)。从函数返回也需要被调用的函数和调用函数执行差不多工作量。调用函数和从函数返回所需的工作量称为“额外开销”,因为我们并没有要求函数执行这些工作。尽管函数调用中的额外开销只是使程序稍许变慢,但在特定的情况下额外开销会产生累积效应。例如,在函数需要调用数百万次或数十亿次,使用老式的比较慢的处理器(例如在嵌套系统中),或者有着非常严格的时限要求(例如在实时系统中)时。

C89中,避免函数额外开销的唯一方式是使用带参数的宏,但是带参数的宏也有一些缺点。C99提供了一种更好的解决方案:创建内联函数(inline function)。“内联”表明编译器把函数的每一次调用都用函数的机器指令来代替。这种方法虽然会使被编译程序的大小增加一些,但是可以避免函数调用的常见额外开销。

不过,把函数声明为inline并不是强制编译器将代码内联编译,只是建议编译器应该使函数调用尽可能地快,也许在函数调用时才执行内联展开。编译器可以忽略这一建议。从这方面来说,inline类似于registerrestrict关键字,后两者也是用于提升程序性能的,但可以忽略。

内联定义

内联函数用关键字inline作为一个声明说明符:

inline double average(double a, double b)
{
    return (a + b) / 2;
}

下面考虑复杂一点的情形。average有外部链接,所以在其他源文件也可以调用average。但是编译器并没有考虑average的定义是外部定义(它是内联定义),所以试图在别的文件中调用average将被认为是错误的。

有两种方法可以避免这一错误。一种方法是在函数定义中增加单词static

static inline double average(double a, double b)
{
    return (a + b) / 2;
}

现在average具有内部链接了,所以其他文件不能调用它。其他文件可以定义自己的average函数,可以与这里的定义相同,也可以不同。

另一种方法是为average提供外部定义,从而可以在其他文件中调用。一种实现方式是将该函数重新写一遍(不使用inline),并将这一函数定义放在另一源文件中。这样做是合法的,但为同一个函数提供两个版本不太可取,因为我们不能保证对程序进行修改时它们仍然一致。

更好一些的实现方式是,首先将average的内联定义放入头文件(命名为average.h)中:

#ifndef AVERAGE_H
#DEFFINE AVERAGE_H

inline double average(double a, double b)
{
    return (a + b) / 2;
}

#endif

接下来,再创建与之匹配的源文件average.c

#include "average.h"

extern double average(double a, double b);

现在,任何一个需要调用average函数的文件只需要简单地包含average.h就行了,该头文件包含了average的内联定义。average.c文件包含了average的原型。由于使用了extern关键字,因此average.h中的average的定义在average.c中被认为是外部定义。

C99中的一般法则是,如果特定文件中某个函数的所有顶层声明中都有inline但没有extern,则该函数定义在该文件中是内联的。如果在程序的其他地方使用该函数(包含其内联定义的文件也算),则需要在另一个文件中为其提供外部定义。调用函数时,编译器可以选择进行正常调用(使用函数的外部定义)或者执行内联展开(使用函数的内联定义)。我们没有办法知道编译器会怎样选择,所以一定要确保这两外定义一致。刚刚我们讨论的方式(使用average.haverage.c)可以保证定义的一致性。

对内联函数的限制

因为内联函数的实现方式和一般函数大不一样,所以需要一些不同的规则和限制。对于具有外部链接的内联函数来说,具有静态存储期限的变量是一个特别的问题。因此,C99对具有外部链接的内联函数(未对具有内部链接的内敛函数做约束)做了如下限制。

  • 函数中不能定义可改变的static变量。
  • 函数中不能引用具有内部链接的变量。

这样的函数可以定义同时为staticconst的变量,但是每个内联定义都需要分别创建该变量的副本。

GCC中使用内联函数

C99标准之前,一些编译器(包括GCC)已经可以支持内联函数了。因此,它们使用内联函数的规则可能与C99标准不一样。特别是,前面描述的那种方案(使用average.h和average.c文件)在这些编译器中可能无效。预计4.3版本的GCC可以按C99标准描述的方式支持内联函数。

不论GCC的版本如何,被同时定义为staticinline的函数都可以工作得很好。这样做在C99中也是合法的,所以是最安全的。static inline函数可以用于单个文件,也可以放在头文件中然后在需要调用的源文件中包含进去。

还有一种方法可以在多个文件中共享内联函数。这种方法适用于旧版本的GCC,但是与C99相冲突。做法是:将函数的定义放入头文件中,指明其为externinline:然后,在任何包含该函数调用的源文件中包含该头文件,并且在其中一个源文件中再次给出该函数的定义(不过这次没有externinline关键字)。这样即便编译器因为某种原因不能对函数进行“内联”,函数仍然有定义。

关于GCC最后需要注意的是:仅当通过-o命令行选项请求进行优化时,才会对函数进行“内联”。

posted @   木凌云  阅读(136)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示