内联函数inline
在机器层面,调用函数之前可能需要预先执行一些指令。调用本身需要跳转到函数的第一条指令,函数本身可能也需要执行一些格外的指令来启动执行。如果函数有参数,参数需要被复制(因为C
通过值传递参数)。从函数返回也需要被调用的函数和调用函数执行差不多工作量。调用函数和从函数返回所需的工作量称为“额外开销”,因为我们并没有要求函数执行这些工作。尽管函数调用中的额外开销只是使程序稍许变慢,但在特定的情况下额外开销会产生累积效应。例如,在函数需要调用数百万次或数十亿次,使用老式的比较慢的处理器(例如在嵌套系统中),或者有着非常严格的时限要求(例如在实时系统中)时。
在C89
中,避免函数额外开销的唯一方式是使用带参数的宏,但是带参数的宏也有一些缺点。C99
提供了一种更好的解决方案:创建内联函数(inline function)
。“内联”表明编译器把函数的每一次调用都用函数的机器指令来代替。这种方法虽然会使被编译程序的大小增加一些,但是可以避免函数调用的常见额外开销。
不过,把函数声明为inline
并不是强制编译器将代码内联编译,只是建议编译器应该使函数调用尽可能地快,也许在函数调用时才执行内联展开。编译器可以忽略这一建议。从这方面来说,inline
类似于register
和restrict
关键字,后两者也是用于提升程序性能的,但可以忽略。
内联定义
内联函数用关键字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.h
和average.c
)可以保证定义的一致性。
对内联函数的限制
因为内联函数的实现方式和一般函数大不一样,所以需要一些不同的规则和限制。对于具有外部链接的内联函数来说,具有静态存储期限的变量是一个特别的问题。因此,C99
对具有外部链接的内联函数(未对具有内部链接的内敛函数做约束)做了如下限制。
- 函数中不能定义可改变的
static
变量。 - 函数中不能引用具有内部链接的变量。
这样的函数可以定义同时为static
和const
的变量,但是每个内联定义都需要分别创建该变量的副本。
在GCC
中使用内联函数
在C99
标准之前,一些编译器(包括GCC
)已经可以支持内联函数了。因此,它们使用内联函数的规则可能与C99
标准不一样。特别是,前面描述的那种方案(使用average.h和average.c文件)在这些编译器中可能无效。预计4.3版本的GCC
可以按C99
标准描述的方式支持内联函数。
不论GCC
的版本如何,被同时定义为static
和inline
的函数都可以工作得很好。这样做在C99
中也是合法的,所以是最安全的。static inline
函数可以用于单个文件,也可以放在头文件中然后在需要调用的源文件中包含进去。
还有一种方法可以在多个文件中共享内联函数。这种方法适用于旧版本的GCC
,但是与C99
相冲突。做法是:将函数的定义放入头文件中,指明其为extern
和inline
:然后,在任何包含该函数调用的源文件中包含该头文件,并且在其中一个源文件中再次给出该函数的定义(不过这次没有extern
和inline
关键字)。这样即便编译器因为某种原因不能对函数进行“内联”,函数仍然有定义。
关于GCC
最后需要注意的是:仅当通过-o
命令行选项请求进行优化时,才会对函数进行“内联”。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)