在C中使用`inline`关键字有什么用?

我在stackoverflow中读了几个关于C中inline问题,但仍然不清楚。

  1. static inline void f(void) {}static void f(void) {}没有实际区别。
  2. inline void f(void) {}不能像C ++那样工作。 它在C中如何工作?
  3. 实际上extern inline void f(void); 做?

我从来没有真正在我的C程序中使用inline关键字,当我在其他人的代码中看到这个关键字时,它几乎总是static inline ,其中我看到只有static没有区别。

注意:当我在这个答案中谈论.c文件和.h文件时,我假设您已经正确布置了代码,即.c文件只包含.h文件。 区别在于.h文件可以包含在多个翻译单元中。

static inline void f(void) {}static void f(void) {}没有实际区别。

在ISO C中,这是正确的。 它们的行为相同(假设您当然不会在同一个TU中重新声明它们!)唯一的实际效果可能是使编译器以不同方式进行优化。

inline void f(void) {}不能像C ++那样工作。 它在C中如何工作? 实际上extern inline void f(void); 做?

这个答案也解释了这个问题 。

在ISO C和C ++中,您可以在头文件中自由使用inline void f(void) {} – 尽管出于不同的原因!

在ISO C中,它根本不提供外部定义。 在ISO C ++中,它确实提供了外部定义; 但是C ++有一个额外的规则(C没有),如果inline函数有多个外部定义,那么编译器会将其排序并选择其中一个。

extern inline void f(void); 在ISO C中的.c文件中,与头文件中的inline void f(void) {}配合使用。 它导致函数的外部定义在该转换单元中发出。 如果你不这样做,那么没有外部定义,因此你可能会收到一个链接错误(未指定f任何特定调用是否链接到外部定义)。

换句话说,在ISO C中,您可以手动选择外部定义的位置; 或者通过在任何地方使用static inline完全抑制外部定义; 但是在ISO C ++中,编译器选择是否以及外部定义的位置。

在GNU C中,情况有所不同(下面有更多内容)。

为了进一步复杂化,GNU C ++允许你在C ++代码中编写static inline extern inline …我不想猜测它到底是什么

我从来没有真正在我的C程序中使用inline关键字,当我在其他人的代码中看到这个关键字时,它几乎总是静态内联

许多程序员不知道他们在做什么,只是把看起来有用的东西放在一起。 另一个因素是您正在查看的代码可能是为GNU C而不是ISO C编写的。

在GNU C中 ,plain inline行为与ISO C不同。它实际上发出一个外部可见的定义,因此从两个转换单元中包含一个带有简单inline函数的.h文件会导致未定义的行为。

因此,如果编码器想要在GNU C中提供inline优化提示,则需要static inline 。 由于static inline在ISO C和GNU C中均可使用,因此很自然人们最终解决了这个问题并且看到它似乎在没有错误的情况下工作。

,其中我认为只有静态没有区别。

不同之处仅在于为编译器提供速度超过大小的优化提示。 使用现代编译器,这是多余的。

AC代码可以通过两种方式进行优化:代码大小和执行时间。

内联函数:

gcc.gnu.org说,

通过声明内联函数,您可以指示GCC更快地调用该函数。 GCC可以实现这一目标的一种方法是将该函数的代码集成到其调用者的代码中。 这通过消除函数调用开销使得执行更快; 此外,如果任何实际参数值是常量,则它们的已知值可能允许在编译时进行简化,因此不需要包括所有内联函数的代码。 对代码大小的影响不太可预测; 根据具体情况,具有函数内联的目标代码可以更大或更小。

因此,它告诉编译器将函数构建到使用它的代码中,以便缩短执行时间。

如果您声明小型函数,如设置/清除标志或重复执行的某些位切换( inline ,它可以在时间方面产生很大的性能差异,但代价是代码大小。


非静态内联和静态内联

再次提到gcc.gnu.org ,

当内联函数不是静态函数时,编译器必须假定可能存在来自其他源文件的调用; 由于全局符号只能在任何程序中定义一次,因此不能在其他源文件中定义该函数,因此无法集成其中的调用。 因此,非静态内联函数总是以通常的方式自行编译。


外部内联?

同样, gcc.gnu.org ,说明了一切:

如果在函数定义中同时指定了inline和extern,则该定义仅用于内联。 在任何情况下,函数都不会自行编译,即使您明确地引用其地址也是如此。 这样的地址成为外部引用,就好像您只声明了该函数,并且没有定义它。

内联和外部的这种组合几乎具有宏的效果。 使用它的方法是将函数定义放在带有这些关键字的头文件中,并将定义的另一个副本(缺少内联和外部)放在库文件中。 头文件中的定义导致对函数的大多数调用都被内联。 如果函数的任何使用仍然存在,则它们引用库中的单个副本。


把它们加起来:

  1. 对于inline void f(void){}inline定义仅在当前转换单元中有效。
  2. 对于static inline void f(void) {}由于存储类是static ,因此标识符具有内部链接,并且inline定义在其他转换单元中是不可见的。
  3. 对于extern inline void f(void); 由于存储类是extern ,标识符具有外部链接,并且内联定义还提供外部定义。

来自6.7.4 C11规范中的函数说明符

6使用内联函数说明符声明的函数是内联函数。 使函数成为内联函数表明对函数的调用尽可能快。 138)这些建议有效的程度是实施定义的139)

138)例如,通过使用通常的函数调用机制的替代方法,例如内联替换 内联替换不是文本替换 ,也不是创建新函数。 因此,例如,在函数体内使用的宏的扩展使用它在函数体出现时的定义,而不是调用函数的位置; 和标识符指的是身体发生范围内的声明。 同样,该函数具有单个地址,而不管除外部定义之外还发生的内联定义的数量。

139)例如,实现可能永远不会执行内联替换 ,或者可能只对内联声明范围内的调用执行内联替换。

它建议编译器这个函数被广泛使用,并要求在调用此函数时更喜欢速度。 但是对于现代智能编译器,这可能或多或少无关紧要,因为编译器可以决定是否应该内联函数并且可以忽略来自用户的内联请求,因为现代编译器可以非常有效地决定如何调用函数。

static inline void f(void) {}static void f(void) {}没有实际区别。

所以对于现代编译器来说,大部分时间都没有。 对于任何编译器, 没有实际/可观察的输出差异。

inline void f(void) {}不能像C ++那样工作。 它在C中如何工作?

内联在任何地方的函数必须在C ++中随处可见,并且链接器不会抱怨多个定义错误(定义必须相同)。

实际上extern inline void f(void); 做?

这将为f提供外部联系。 因为f可能存在于其他编译单元中,所以编译器可以选择不同的调用机制来加速调用或者可以完全忽略inline

一个函数,其中所有声明(包括定义)都提到内联而不是extern。
同一翻译单元中必须有一个定义。 标准将此称为内联定义。
不会发出任何独立的对象代码,因此无法从其他翻译单元调用此定义。

在此示例中,所有声明和定义都使用内联但不使用extern:

 // a declaration mentioning inline inline int max(int a, int b); // a definition mentioning inline inline int max(int a, int b) { return a > b ? a : b; } 

这是一个参考,可以让您更清楚地了解C中的内联函数以及内联和外部的使用。

单词“Inline”表示“In”“Line”,将此关键字添加到函数会影响运行时的程序,当编译程序时,写入内部函数的代码将粘贴在函数调用下,因为函数调用比内联代码,这样可以优化代码。 因此,静态内联void f(void){}和static void f(void){},在这个内联关键字中确实在运行时有所不同。 但是当函数有太多代码行时,它不会影响运行时。 如果在函数之前添加静态,函数的生命周期就是整个程序的生命周期。 并且该function仅限于该文件。 要了解extern,您可以参考 – extern关键字对C函数的影响