声明(十二)
五、函数说明符
C语言存在两个函数说明符(function specifiers):inline、_Noreturn。使用inline函数说明符声明的函数称为内联函数(inline function);使用_Noreturn函数说明符声明的函数称为无返回函数(no return function)。函数声明或者函数定义中相同的函数说明符出现多次,其行为与出现一次是相同的。
ISO/IEC 9899:2024标准对函数说明符使用作了一些限制:
-- 函数说明符只能用于函数标识符的声明。
-- 宿主环境(hosted environment)中,不能对main函数使用函数说明符。
1、inline函数说明符
使用inline函数说明符将函数声明为内联函数意味着对该函数的调用应尽可能快。编译器优化的一个常用方法是内联替换(inline substitution),即在函数调用处,使用函数体代码替换函数调用。内联替换不是文本替换(textual substitution),也不会创建新函数。内联替换时,函数体内宏展开使用的是宏在函数体内出现时的定义,而不是函数调用时的定义;其它标识符使用的是函数调用所在作用域内该标识符的声明。
gch.h头文件
#define N 5 int i = 10; inline int f(int i) { return i*N; }
gch.c源文件
#include <stdio.h> #include "gch.h" int main(void) { printf("%d\n", f(i)); //输出50。N值为5,i值为10。 #ifdef N #undef N #define N 8 #endif int i = 7; printf("%d\n", f(i)); //输出35。N值为5,i值为7。 return 0; }
上述代码使用Visual Studio编译。
无论外部定义外存在多少内联定义,函数都只有一个地址。
inline函数说明符建议的有效程度取决于实现。实现可能永远不会执行内联替换,也可能只对内联声明范围内的调用执行内联替换。
无论是否优化,函数一旦使用inline函数说明符声明为内联函数,就必须遵守以下规则:
-- 任何具有内部链接的函数都可以是内联函数。
static inline int func(void);
-- 具有外部链接的函数如果声明为内联函数,应受到以下限制:
① 如果具有外部链接的函数声明为内联函数,则函数应在相同编译单元内定义。
C程序通常在头文件中定义内联函数,在源文件中声明该函数,并包含相应头文件。
gch.h头文件
inline int func(int number) { return number; }
gch.c源文件
#include <stdio.h> #include "gch.h" extern inline int func(int); int main(void) { printf("%d\n", func(10)); return 0; }
② 如果同一编译单元内对同一函数的所有具有文件作用域的声明都包含inline函数说明符,且没有extern存储类说明符,则该编译单元内该函数定义是内联定义(inline definition)。内联定义不会提供对应函数的外部定义(external definition),也不会禁止其它编译单元的外部定义。内联定义提供了外部定义的替代方案,编译器可以使用内联定义实现对同一编译单元内该函数的任何调用。具有外部链接的内联函数的声明可能生成外部定义,也可能生成仅在编译单元内可用的定义。ISO/IEC 9899:2024标准并未明确指出函数调用是使用内联定义,还是外部定义。
gch.h头文件
inline int pos(int i) { return i; }
gch.c源文件
#include <stdio.h> #include "gch.h" extern int pos(int i); //声明外部定义。 inline int neg(int i) //内联定义。 { return -i; } int Abs(int i) { return i>0 ? pos(i) : neg(i); } int main(void) { printf("%d\n", Abs(-5)); printf("%d\n", Abs(8)); return 0; }
上述代码使用Visual Studio编译。
-- 具有外部链接函数的内联定义不得包含具有静态存储期限或者线程存储期限的可修改对象的定义,也不得引用具有内部链接的标识符。
static int number; inline void func(void) { static const double pi = 3.14; //合法。 static int i; //非法。 int *ptr = &number; //非法。 }
由于内联定义不同于对应的外部定义,也不同于其它编译单元内对应的内联定义,具有静态存储期限的对象在每个定义中可能是不同的。
内联函数的优点和缺点
内联函数最大的优点是节省函数调用开支;最大的缺点是增加程序代码,如果程序代码增加过多,反过来又会降低程序效率,所以内联函数比较适合小函数。
2、_Noreturn函数说明符
使用_Noreturn函数说明符可以将函数声明为无返回函数;无返回函数表示该函数调用后程序的控制权不会返回给调用者,例如:exit、_Exit、quick_exit、abort等库函数都是无返回函数。实现应为使用_Noreturn函数说明符声明的函数生成一条诊断消息。
_Noreturn void func(void) { exit(EXIT_SUCCESS); } int main(void) { puts("Preparing to exit ..."); func(); /*以下部分不会被执行。*/ puts("After the no return function."); return 0; }
func函数调用后,程序正常终止;main函数中func函数调用以后的代码不会被执行。
<stdnoreturn.h>头文件中定义了宏noreturn,该宏会扩展为C语言关键词_Noreturn,在包含<stdnoreturn.h>头文件的情况下,可以使用宏noreturn替换C语言关键词_Noreturn。
#include <stdlib.h> #include <stdnoreturn.h> noreturn void func(void) { exit(EXIT_SUCCESS); }
属性[[noreturn]]提供了类似语义。根据ISO/IEC 9899:2024标准,使用_Noreturn函数说明符是过时做法。
[[noreturn]] void func(void) { exit(EXIT_SUCCESS); }