C 与 C++ 的 inline 关键字全解

编译器的行为以 gcc/g++ 9.4.0 为准。

目录

1 前置知识

2 C 的 inline

2.1 C 的 inline 作为函数说明符(C99 起)

2.1.1 非 static 非 extern 的内联函数

2.1.2 extern 非 static 的内联函数

2.1.3 static 非 extern 的内联函数

2.2 属性说明符序列中的 inline(C23)

3 C++ 的 inline

3.1 C++ 内联函数

3.2 C++ 内联变量

3.3 内联函数 VS 宏

3.4 属性说明符序列中的 inline(C++11 起)

4 References


1 前置知识

C 与 C++ 的 static 关键字全解_yihuajack的博客-CSDN博客1 前置知识参考本系列的上一篇文章:C 与 C++ 的 extern 关键字全解_yihuajack的博客-CSDN博客编译器的行为以 gcc/g++ 9.4.0 为准。1 前置知识1.1 声明、定义与初始化声明(声明式)= declarationextern int x;std::size_t numDigits(int number);class Widget;template<typename T>class GraphNode;定义(定义式)= definitionint x;shttps://blog.csdn.net/yihuajack/article/details/122330115?spm=1001.2014.3001.5501

2 C 的 inline

2.1 C 的 inline 作为函数说明符(C99 起)

inline 说明符的目的是提示编译器做优化,若编译器进行函数内联,则它会以函数体取代所有对它的调用,以避免函数调用的开销。内联函数保证以下语义:

(1) 任何拥有内部链接的函数都可以声明成 static inline(对于 C 而言,只有 static 函数拥有内部链接,也就是说只要是 static 函数都可以声明成 static inline 函数)。
(2) 一个非 static 的内联函数不能定义一个非 const 的函数局部 static 对象,并且不能使用文件作用域的 static 对象。

不同翻译单元中的内联定义不受一个定义规则约束。

和静态函数一样,内联函数可以用于在头文件中定义函数,并由同一程序的多个翻译单元包含该头文件。

有些函数定义不能被内联:
(1) 可变参数函数(Variadic Function)
(2) 用到了 alloca
(3) 用到了 Computed goto(没有正式的中文译名,GNU 对 C 的一个扩展,实际上是 Fortran 的 Assigned goto 的超集,可参考 Goto - Wikipedia
(4) 用到了非局部 goto
(5) 用到了嵌套函数
(6) 用到了 setjmp
(7) 用到了 __builtin_longjmp
(8) 用到了 __builtin_return 或 _builtin_apply_args

2.1.1 非 static 非 extern 的内联函数

与一般的 C 函数默认 extern 不同,非 extern 声明的内联函数定义对外部(其它翻译单元/文件)不可见,因此其它翻译单元可以定义相同名字的函数(但不应该这样做,这样做也没有意义)。

注意:与 C99 及 GNU99 不同,GNU89 中非 static 非 extern 的内联函数定义是外部可见的,因此只要在翻译单元中定义无需声明就可以使用。

非 static 非 extern 的内联函数的内联定义本身不提供外部定义。例如定义一个内联函数

inline int foo() {
    int x = 5;
    return x;
}

如果将该内联函数定义放在源文件中并在同一个源文件或另一个源文件中使用该函数

int main() {
    int x = foo();
    printf("%d", x);
    return 0;
}

那么 gcc 编译器会报错误 undefined reference to `foo';如果在一个源文件中声明 inline int foo,在另一个源文件中定义 foo 会警告 inline function 'foo' declared but never defined。因此,必须对函数 foo 至少在本翻译单元再声明一遍才可以使用(声明和定义只要都放在一个翻译单元内即可,可以都放在源文件或头文件中,也可以分别放在源文件和头文件中,可以不带 extern 因为 C 函数声明默认 extern,其它联合编译的源文件也可以通过声明或包括带声明的头文件来使用该内联函数;如果声明了它就必须在同一翻译单元中定义它,否则会产生未定义行为,见 ISO/IEC 9899:1999)

extern inline 声明会生成外部定义;内联函数的地址始终是外部定义的地址,但当以此地址进行函数调用时,而调用内联定义(如果翻译单元中存在)还是外部定义是未指定的)。

实际上,非 static 非 extern 函数极少在代码中使用(一般用 static inline),一般的用法是是在头文件中定义这样的函数并在需要使用该函数的源文件中使用 extern 或 extern inline 声明该函数(对于 C,若函数在一些翻译单元中声明为 inline ,它就不需要在处处皆声明为 inline)。

非 static 的内联函数不能定义一个非 const 的函数局部 static 对象,并且不能使用文件作用域的 static 对象:

static int x;
inline void f() {
    static int n = 1;  // error!
    int k = x;  // error!
}

以上内联函数内的两条定义均错误。

对于 GNU C90(即 GNU89 或 GNU90)的 非 static 内联函数,编译器必须假设其它源文件会调用该函数;由于程序中全局符号只能被定义一次,函数不能在其它源文件中定义,所以这样的调用不能被整合在一起;非 static 内联函数总是单独编译

2.1.2 extern 非 static 的内联函数

extern int inc(int *a);
inline int inc(int *a) {
    return (*a)++;
}

extern 为内联函数提供了外部链接。

extern inline 函数和 extern 函数除了编译器可能的优化不同外没有区别。

与非 extern 非 static 的内联函数不同的是,可以直接用 extern inline 在头文件或源文件定义一个函数而直接使用它而无需额外声明,但是同样的在联合编译时如果一个源文件要调用另一个源文件的 extern inline 时仍然需要声明。 

对于 GNU C90,extern 内联函数定义只用于内联,它不能单独编译,即使显式地引用它的地址(由于你只声明了该函数而未定义,所以这样的地址是外部引用)。它和宏有几乎相同的效果,使用它的方法是把函数定义放在头文件里,把不带 extern 和 inline 的函数定义拷贝放在库文件中。

2.1.3 static 非 extern 的内联函数

static inline int inc(int *a) {
    return (*a)++;
}

static 为内联函数提供了内部链接。

对于 ISO C 而言,static inline 函数和 static 函数除了编译器可能的优化不同外没有任何区别。

与 extern 非 static 的内联函数一样,可以直接用 static inline 在头文件或源文件定义一个函数而直接使用它而无需额外声明;与非 extern 非 static 的内联函数一样,(非 extern)static inline 只对当前翻译单元可见,其它文件不能使用。

2.2 属性说明符序列中的 inline(C23)

C23 允许的 inline 属性包括:
1. [[gnu::always_inline]],对应的是 GNU C 标准的 always_inline 属性,对应的关键字是 __forceinline:

inline void foo (const char) __attribute__((always_inline));

也可以在函数声明前写

__attribute__((always_inline))

作用是:试探性内联被禁用,无论优化等级为何都会尝试进行内联,但并不保证总会内联。 

2. [[gnu::gnu_inline]] ,对应的是 GNU C 标准的 gnu_inline 属性,作用是将 extern inline 改为 GNU 的内联语义,即
a. 任何非 extern 的内联声明中 inline 关键字只是一个提示。就是如前所述的 GNU89 和 C99 在非 static 非 extern 的内联函数的区别——GNU89 的语义中非 static 非 extern 的内联函数具有外部链接(out-of-line version)。
b. extern 的内联声明不会导致 out-of-line version。

3 C++ 的 inline

因为关键词 inline 的含义是非强制的,编译器拥有对任何未标记为 inline 的函数使用内联替换的自由,和对任何标记为 inline 的函数生成函数调用的自由。

(1) 内联函数或变量的定义必须在访问它的翻译单元中可达
(2) inline 说明符不能重声明在翻译单元中已定义为非内联的函数或变量
(3) inline 说明符不能用于块作用域内(函数内部)的函数或变量声明

带外部链接的内联函数或变量:
(1) 在程序中可有多于一次定义,只要每个定义都出现在不同翻译单元中(对于非静态的内联函数和变量)且所有定义等同即可(定义不同为导致未定义行为,例如 G++ 是取其中一个定义)。例如,内联函数或内联变量可定义于被多个源文件所 #include 的头文件中。
(2) 必须在每个翻译单元中都被声明为 inline。
(3) 每个翻译单元中都拥有相同的地址。

用法:在头文件中
(1) 定义将被多个源文件包含的函数必须为 inline
(2) 拥有外部链接的包含于多个源文件的变量必须为 inline

3.1 C++ 内联函数

inline 说明符,在用于函数的 声明说明符序列 时,将函数声明为一个 内联(inline)函数。

声明有 constexpr 的函数是隐式的内联函数。

a. 所有函数定义中的函数局部静态对象在所有翻译单元间共享(它们都指代相同的定义于某一个翻译单元中的对象)
b. 所有函数定义中所定义的类型同样在所有翻译单元中相同

命名空间作用域的内联 const 变量默认具有外部链接。

根据 ISO C++ 的要求,完全定义在 class 或 struct 或 union 的定义内的函数是隐式的内联函数(例如定义在类体内的成员函数或非成员友元函数),即即使它们不被显示声明为 inline 也会被认为是 inline 声明的,不需要再重复声明为 inline。自 C++ 20 起,它们必须要被附着到全局模块才是隐式的。相对而言,

class S {
public:
    int square(int s);
};
  
inline int S::square(int s) {}

如果要为类成员函数显式声明为 inline 应在类体外作定义。

不同于 C,C++ 的 inline 函数不声明为 static 就具有外部链接。不带 extern 或 static 的 inline 函数不需要再次声明就可以直接在本翻译单元内直接使用(仍要在其它翻译单元声明),因为它提供外部定义

以下函数编译器往往不会作内联:
(1) 包含循环
(2) 包含静态变量
(3) 递归的
(4) 返回类型非空,但函数本身不返回
(5) 包含 switch 语句或 goto

GNU C++ 像 C 一样额外允许 extern inline 和 static inline。

3.2 C++ 内联变量

inline 说明符,在用于具有静态存储期的变量(静态类成员或命名空间作用域变量)的 声明说明符序列 时,将变量声明为内联变量。

声明为 constexpr 的静态成员变量(但不是命名空间作用域变量)是隐式的内联变量。

命名空间作用域的内联 const 变量默认具有外部链接。

3.3 内联函数 VS 宏

1. 内联函数遵循类型安全协议
2. 内联函数和普通函数有完全相同的语法
3. 内联函数的参数只被求值一次,但宏则不一定

3.4 属性说明符序列中的 inline(C++11 起)

与 C 的属性说明符序列中的 inline 相同。

4 References

[1] GeeksforGeeks. Inline function in C. GeeksforGeeks, Dec. 3, 2018. URL: https://www.geeksforgeeks.org/inline-function-in-c/.
[2] Sven Marnach et al. Is "inline" without "static" or "extern" ever useful in C99? Jan. 29, 2020. URL: https://stackoverflow.com/questions/6312597/is-inline-without-static-or-extern-ever-useful-in-c99.
[3] Free Software Foundation. 6.45 An Inline Function is As Fast As a Macro. Free Software Foundation, 2022. URL: https://gcc.gnu.org/onlinedocs/gcc/Inline.html.
[4] The Clang Team. Attributes in Clang. The Clang Team, 2022. URL: https://clang.llvm.org/docs/AttributeReference.html.
[5] Cppreference Contributors. Attribute specifier sequence(since C23) — Cppreference. [Online; accessed 7-January-2022]. Dec. 8, 2021. URL: https://en.cppreference.com/w/c/language/attributes.
[6] Cppreference Contributors. Inline function specifier — Cppreference. [Online; accessed 7-January-2022]. June 24, 2021. URL: https://en.cppreference.com/w/c/language/inline.
[7] Meet Pravasi. Inline Functions in C++. GeeksforGeeks, Sept. 7, 2018. URL: https://www.geeksforgeeks.org/inline-functions-cpp.
[8] Cppreference Contributors. Inline specifier — Cppreference. [Online; accessed 8-January-2022]. Sept. 2, 2021. URL: https://en.cppreference.com/w/cpp/language/inline.
[9] Colin Robertson et al. Inline Functions (C++). Microsoft, Aug. 4, 2021. URL: https://docs.microsoft.com/en-us/cpp/cpp/inline-functions-cpp?view=msvc-170.
[10] user3810155 et al. What is the use of the `inline` keyword in C? June 20, 2020. URL: https://stackoverflow.com/questions/31108159/what-is-the-use-of-the-inline-keyword-in-c.
[11] wilbur_m et al. What does extern inline do? Dec. 31, 2019. URL: https://stackoverflow.com/questions/216510/what-does-extern-inline-do.
[12] Wikipedia Contributors. Inline function — Wikipedia, The Free Encyclopedia. [Online; accessed 8-January-2022]. Nov. 5, 2021. URL: https://en.wikipedia.org/wiki/Inline_function.
[13] Wikipedia Contributors. Inline expansion — Wikipedia, The Free Encyclopedia. [Online; accessed 8-January-2022]. Dec. 25, 2021. URL: https://en.wikipedia.org/wiki/Inline_expansion.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ayka

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值