我在stackoverflow中读到了几个关于C中inline
的问题,但仍然不清楚。
static inline void f(void) {}
与static void f(void) {}
没有实际差别。inline void f(void) {}
在C中的工作方式与C++不同。它在C中是如何工作的?extern inline void f(void);
的实际功能是什么?
我从来没有在我的C程序中真正发现过inline
关键字的使用,当我在其他人的代码中看到这个关键字时,它几乎总是static inline
,我看不出它与static
有什么区别。
6条答案
按热度按时间js5cn81o1#
可以通过两种方式优化C代码:对于代码大小和执行时间。
内联函数:
gcc.gnu.org说,
通过声明一个内联函数,你可以指示愚者更快地调用该函数。愚者可以实现这一点的一种方法是将该函数的代码集成到其调用者的代码中。这通过消除函数调用的开销而使执行更快;此外,如果任何实际参数值是常量,则它们的已知值可以允许在编译时进行简化,从而不需要包括内联函数的所有代码。根据具体情况,对象代码可以更大或更小,具有函数内联。
因此,它告诉编译器将函数构建到使用它的代码中,以提高执行时间。
如果您声明重复执行的Small函数(如设置/清除标志或某些位切换),
inline
,则会在时间方面产生很大的性能差异,但代价是代码大小。非静态内联和静态内联
再次参考gcc.gnu.org,
当一个内联函数不是静态的,那么编译器必须假设可能有来自其他源文件的调用;由于一个全局符号在任何程序中只能定义一次,因此该函数不能在其他源文件中定义,因此其中的调用不能被集成。2因此,非静态内联函数总是以通常的方式独立编译。
外部内联?
同样,gcc.gnu.org说明了一切:
如果在函数定义中同时指定了inline和extern,则该定义仅用于内联。在任何情况下,函数都不会独立编译,即使显式引用了它的地址也是如此。这样的地址将成为外部引用,就好像您只声明了函数,而没有定义它一样。
这种内联和外部的组合几乎有一个宏的效果。使用它的方法是把一个函数定义放在一个带有这些关键字的头文件中,并把另一个定义的副本(没有内联和外部)放在一个库文件中。头文件中的定义使大多数对函数的调用都是内联的。如果函数的任何使用仍然存在,它们将引用库中的单个副本。
总结一下:
1.对于
inline void f(void){}
,inline
定义只在当前翻译单位中有效。1.对于
static inline void f(void) {}
由于存储类是static
,因此标识符具有内部链接,并且inline
定义在其他转换单元中不可见。1.对于
extern inline void f(void);
由于存储类是extern
,因此标识符具有外部链接,并且内联定义也提供外部定义。pod7payv2#
注意:当我在这个答案中谈到
.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);
实际上是做什么的?这可以用this answer和this thread来解释。
在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 inline
和extern inline
......我不想猜测它到底做了什么我从来没有在我的C程序中真正发现过inline关键字的使用,当我在其他人的代码中看到这个关键字时,它几乎总是静态内联的
许多程序员不知道他们在做什么,只是把一些看起来可以工作的东西放在一起。这里的另一个因素是,你正在看的代码可能是为GNU C而不是ISO C编写的。
在GNU C中,纯
inline
的行为与ISO C不同。它实际上发出外部可见的定义,因此,如果.h
文件包含来自两个翻译单元的纯inline
函数,则会导致未定义的行为。因此,如果编码者希望在GNU C中提供
inline
优化提示,那么就需要static inline
。由于static inline
在ISO C和GNU C中都能工作,人们很自然地接受了它,并看到它看起来工作而没有给出错误。,在其中我看不出与刚才的静态有什么区别。
不同之处只是为了给编译器提供一个速度-大小优化的提示。对于现代的编译器来说,这是多余的。
siv3szwd3#
来自C11规范中的6.7.4函数说明符
6用内联函数说明符声明的函数是内联函数。使函数成为内联函数建议对函数的调用尽可能快。138)这种建议的有效程度由实现定义。139)
138)例如,通过使用通常的函数调用机制的替换,诸如 * 内联替换 *。内联替换不是文本替换,也不创建新函数。因此,例如,在函数体内使用的宏的扩展使用其在函数体出现时所具有的定义,而不是在调用函数的地方所具有的定义;和标识符引用函数体所在作用域中的声明。同样,函数只有一个地址,而不管除了外部定义之外还有多少内联定义。
139)例如,实现可能从不执行内联替换,或者可能只对内联声明范围内的调用执行内联替换。
它建议编译器该函数被广泛使用,并要求在调用该函数时优先考虑速度。但对于现代智能编译器,这可能或多或少无关紧要,因为编译器可以决定一个函数是否应该内联,并可能忽略用户的内联请求,因为现代编译器可以非常有效地决定如何调用该函数。
static inline void f(void) {}
与static void f(void) {}
没有实际差别。所以现代编译器大多数时候都没有。任何编译器都没有实际的/可观察到的输出差异。
inline void f(void) {}
在C中的工作方式与C不同。它在C中是如何工作的?在C中,任何地方都内联的函数必须在任何地方都内联,并且链接器不会报告多个定义错误(定义必须相同)。
extern inline void f(void)究竟做了什么;做什么?
这将提供到
f
的外部链接。因为f
可能存在于其他编译单元中,所以编译器可能选择不同的调用机制来加快调用,或者可能完全忽略inline
。iovurdzv4#
所有声明(包括定义)都提到inline而从不提到extern的函数。
同一翻译单元中必须有一个定义。标准将此称为内联定义。
没有发出独立的对象代码,因此不能从另一个翻译单元调用此定义。
在此示例中,所有声明和定义都使用inline而不是extern:
Here是一个参考,它可以给予你更清楚地了解C中的内联函数,以及内联和外部的用法。
ar7v8xwq5#
如果你明白他们从哪里来,那么你就会明白他们为什么在那里。
“inline”和“const”都是C的创新,最终被改造成了C。这些创新以及后来的创新(如template和lambda)所隐含的设计目标之一是为预处理器(特别是“#define”)开辟出最常见的用例,以便最小化预处理器阶段的使用和需求。
语言中预处理器阶段的出现严重限制了在分析和翻译语言时提供透明性的能力。这使得原本应该容易翻译的shell脚本变成了更复杂的程序,例如“f2c”(Fortran到C)和原始C编译器“cfront”(C到C);如果你曾经不得不处理类似这些转换器的翻译输出(我们也有),或者实际上制作自己的翻译器,那么你就会知道这是一个多么大的问题。
顺便说一句,“indent”实用程序在整个问题上犹豫不决,只是在问题的边缘徘徊,通过将宏调用视为普通变量或函数调用并忽略“#include”来进行妥协。也就是说,可以更智能地自动执行程序员所做的事情。
因此,理想的做法是将对预处理器阶段的依赖性降至最低。这是一个本身就很好的目标,与过去可能遇到的问题无关。
随着时间的推移,随着越来越多的用例变得为人所知,甚至在用法上被标准化,它们被正式封装为语言创新。
“#define”的一个常见用例是创建清单常量。在很大程度上,这现在可以通过“const”关键字和(在C中)“constexpr”来处理。
“#define”的另一个常见用例是创建带有宏的函数。这其中的大部分内容现在都被“inline”函数封装了,这就是它要替换的内容。在C中,“lambda”构造更进一步。
“const”和“inline”在1985年2月C第一次对外发布E版时就已经出现了(我们是转录和恢复它的人,在2016年之前,它只存在于一个几百页的打印输出中,剪辑得很糟糕)。
其他的创新后来被加入,比如cfront 3.0版本中的“template”(已经在1990年的ANSI X3 J16会议上被接受),以及最近的lambda构造和“constexpr”。
chhkpiq46#
正如单词“Inline”表示“In”“Line”,在函数中添加此关键字会影响运行时的程序,当编译程序时,在函数内部编写的代码会粘贴在函数调用下,因为函数调用比内联代码成本更高,所以这会优化代码。
所以,
static inline void f(void) {}
和static void f(void) {}
,这里的inline关键字确实会在运行时产生影响,但是当函数有太多行代码时,它不会影响运行时。如果你在一个函数前面加上
static
,那么这个函数的生命周期就是整个程序的生命周期,而且这个函数的使用只限于这个文件。要了解extern,可以参考-Effects of the extern keyword on C functions