在 C 语言中“ inline”关键字有什么用?

我在堆栈溢出中读到了几个关于 C 语言中 inline的问题,但仍然不清楚。

  1. static inline void f(void) {}static void f(void) {}没有实际差异。
  2. C 语言中的 inline void f(void) {}不能像 C + + 那样工作,那么它在 C 语言中是如何工作的呢?
  3. extern inline void f(void);到底是做什么的?

我从来没有发现在我的 C 程序中使用过 inline关键字,当我在其他人的代码中看到这个关键字时,它几乎总是 static inline,在这里我看不到与 static有什么区别。

70238 次浏览

一个函数,其中所有的声明(包括定义)都在内联中提到,而从不在外部提到。
在同一个翻译单元中必须有一个定义。该标准称之为行内定义。
没有发出独立的对象代码,因此不能从其他转换单元调用此定义。

在这个例子中,所有的声明和定义都使用 inline 而不是 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 & 中的内联函数以及内联和外部函数的用法。

6.7.4函数说明符在 C11规格

用内联函数说明符声明的函数是内联函数 使一个函数成为内联函数意味着调用 函数尽可能快 这些建议是有效的 实现定义的 . < sup > 139)

138)通过使用,例如,一个替代通常的函数调用 内联替换是 < strong > not 文本替换 ,也不会创建新函数。因此, 元素体中使用的宏的展开 函数使用它在函数体上的定义 显示,而不是调用函数的位置; 标识符引用 在主体出现的范围内的声明 函数只有一个地址,与内联的数量无关 除了外部 定义

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

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

static inline void f(void) {}与 < code > static 没有实际区别 Void f (void){} .

所以,对于现代编译器,大多数情况下是没有的。与任何编译器有 没有实际/可观察的输出差异。

C 语言中的 inline void f(void) {}不能像 C + + 那样工作 在 C 区工作?

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

什么实际上是外部内联 void f (void) ; 做什么?

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

正如单词“ Inline”表示“ In”“ Line”一样,将这个关键字添加到函数中会在运行时影响程序,当一个程序被编译时,函数内编写的代码会粘贴到函数调用下面,因为函数调用比内联代码开销更大,所以这会优化代码。

因此,在 static inline void f(void) {}static void f(void) {}中,inline 关键字在运行时确实有所不同。但是当函数的代码行太多时,它不会影响运行时。

如果在函数前面加上 static,函数的生命周期就是整个程序的生命周期。该函数的使用仅限于该文件。

要了解外部你可以参考 -Extern 关键字对 C 函数的影响

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

内联功能:

Gcc.gnu.org 说,

通过内联声明函数,可以指示 GCC 更快地调用该函数。GCC 实现这一点的一个方法是将该函数的代码集成到其调用方的代码中。这样可以消除函数调用开销,从而加快执行速度; 此外,如果任何实际参数值是常数,它们的已知值可能允许在编译时进行简化,因此不需要包含所有内联函数的代码。对代码大小的影响是不可预测的; 函数内联的对象代码可能更大,也可能更小,这取决于具体情况。

因此,它告诉编译器将函数构建到代码中,在那里使用函数是为了提高执行时间。

如果你声明一些重复执行的小函数,比如设置/清除一个标志或者一些比特切换,inline,它可以在时间上产生很大的性能差异,但是代码大小是要付出代价的。


非静态内联和静态内联

再次提到 Gcc.gnu.org,

当一个内联函数不是静态的,那么编译器必须假设可能有来自其他源文件的调用; 因为一个全局符号只能在任何程序中定义一次,所以函数不能在其他源文件中定义,因此其中的调用不能被集成。因此,非静态内联函数总是按照通常的方式自行编译。


外部内联?

Gcc.gnu.org再次说明了一切:

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

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


总结一下:

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

注意: 当我在这个答案中谈到 .c文件和 .h文件时,我假设您已经正确地布局了代码,也就是说,.c文件只包含 .h文件。区别在于 .h文件可能包含在多个翻译单元中。

static inline void f(void) {}static void f(void) {}没有实际差异。

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

C 语言中的 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函数有多个外部定义,编译器就会对其进行排序并选择其中一个。

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

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

在 GNU C 中,情况有所不同(下面将详细介绍)。

更复杂的是,GNU C + + 允许您用 C + + 代码编写 static inlineextern inline... ... 我不想猜测它到底是做什么的

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

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

GNU C中,普通 inline的行为与 ISO C 不同。它实际上发出一个外部可见的定义,因此使用包含两个翻译单元的纯 inline函数的 .h文件会导致未定义的行为。

因此,如果编码人员希望在 GNU C 中提供 inline优化提示,那么就需要 static inline。由于 static inline可以同时在 ISO C 和 GNU C 中工作,所以人们自然而然地接受了它,并且看到它似乎可以正常工作而不会出现错误。

,其中我看不出有什么区别,只是静态。

区别仅仅在于向编译器提供了一个速度过快的优化提示。对于现代编译器来说,这是多余的。

如果你明白他们来自哪里,那么你就会明白他们为什么在那里。

“ inline”和“ const”都是 C + + 的创新,最终被改造成了 C 语言。这些创新以及后来的创新,比如模板的,甚至是 lambda 的,其中隐含的设计目标之一就是为预处理器(尤其是“ # Definition”)开辟出最常见的用例,以便最小化预处理器阶段的使用和需求。

语言中预处理阶段的出现严重限制了在分析和翻译一种语言时提供透明度的能力。这使得原本应该很容易翻译的 shell 脚本变成了更复杂的程序,比如“ f2c”(Fortran to C)和原始的 C + + 编译器“ cfront”(C + + to C) ; 在较小程度上,还有“ indent”实用程序。如果您曾经处理过类似这样的转换器的翻译输出(我们已经处理过) ,或者实际制作自己的翻译器,那么您将知道这是一个多么严重的问题。

顺便说一下,“ indent”实用程序在整个问题上犹豫不决,只是随机应变,只是把宏调用当作普通变量或函数调用来处理,而忽略了“ # include”的调用。这个问题也会出现在其他工具上,这些工具可能需要进行源代码到源代码的转换/翻译,比如自动化的重新工程、重新编码和重构工具; 也就是说,这些工具能够更加智能地自动化你这个程序员所做的事情。

因此,理想的做法是将对预处理器阶段的依赖降低到最低限度。这个目标本身是好的,与过去可能遇到的问题无关。

随着时间的推移,越来越多的用例变得众所周知,甚至标准化了它们的用法,它们被正式封装为语言创新。

创建清单常量的一个常见用例是“ # Definition”。在很大程度上,现在可以通过“ const”关键字和(在 C + + 中)“ Constexpr”来处理这个问题。

“ # Definition”的另一个常见用例是使用宏创建函数。现在大部分内容都被“ inline”函数封装了,这就是它要替换的内容。在 C + + 中,“ lambda”构造更进一步。

从1985年2月第一个外部发布版本 E 开始,“ const”和“ inline”都出现在 C + + 中。(是我们转录并修复了它。在2016年之前,它只是几百页打印出来的残缺页面。)

后来又添加了其他的创新,比如 cfront 3.0版本中的“模板”(已经在1990年的 ANSI X3J16会议上被接受) ,以及最近更新的 lambda 构造和“ Constexpr”。