在机器层面,调用函数之前可能需要预先执行一些指令。调用本身需要跳转到函数的第一条指令,函数本身可能也需要执行一些格外的指令来启动执行。如果函数有参数,参数需要被复制(因为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
)可以保证定义的一致性。