Inline
inline 内联函数
有些函数短小精悍而且调用频繁,调用开销大,经常保护现场,恢复现场,算下来性价比不高。这时就可以将这个函数声明为内联函数。编译器在编译过程中遇到内联函数,像宏一样,将内联函数直接在调用处展开,这样就可以减少函数调用的开支:直接执行内联函数展开的代码,不用再保存现场和恢复现场。
内联函数和宏
内联函数和宏的功能差不多,为什么不直接定义一个宏定义?而是定义一个内联函数?
存在即合理,内联函数在 C 语言中有广泛运用,自然有其原因。与宏相比,内联函数有以下优势:
参数类型检查
内联函数随有宏的展开特性,但其本质仍是函数,在编译过程中,编译器仍可以对其进行参数检查,而宏不具备这个功能。
便于调试
函数支持的调试功能有断点、单步等,内联函数同样支持。
返回值
内联函数有返回值,返回一个结果给调用者。这个优势是相对 ANSI C 说的,因为现在宏也可以有返回值和类型了,如前面使用语句表达式的宏。
接口封装
有些内联函数可以用来封装一个接口,而宏不具备这个功能。
内联函数不足
内联函数并不是完美无瑕的,也存在不足。
- 内联函数会增大函数体积。
如果一个文件中多次调用内联函数,多次展开,那整个程序的体积就会变大,在一定程度上会减低程序的执行效率。 - 减低代码复用性。
函数的作用之一就是提高代码的复用性。我们将一些代码封装成函数,进行模块化编程,可以减轻软件开发的工作量,而内联函数往往又减低函数的复用性。
编译器对内联函数的处理
我们通过 inline
关键字将一个函数声明为内联函数,但编译器不一定会对这个内联函数在进行展开。编译器也要根据实际情况进行评估。除了检测用户定义的内联函数是否含有指针、循环、递归,还会在函数执行效率和函数调用开销之间进行权衡。
从程序员角度出发,是否展开主要考虑如下因素:
- 函数体积大小
- 函数体内无指针赋值、递归、循环等语句
- 调用频繁
当我们认为一个函数体积小,而且被大量调用,应该做内联展开。
属性声明:noinline
明确告诉编译器不展开内联函数
属性声明:always_inline
明确告诉编译器展开内联函数
内联函数定义在头文件中
在Linux 内核中,有大量的内联函数被定义在头文件中,而且经常使用 static
修饰。
Q: 为什么内联函数要定义在头文件中?
A: 因为它是一个内联函数,可以像宏一样展开,任何像使用这个内联函数的源文件,都不必亲自去定义一遍,直接包含这个头文件即可,即像宏定义一样使用。
Q:为什么要加static
修饰
inline 定义的内联函数不一定会展开,当一个工程中多个文件都包含这个内联函数的定义时,编译时就有可能报重定义错误。使用 static 关键字修饰,则可以将这个函数的作用域限制在各自的文件内,避免重定义错误的发生。