为什么要将C函数声明为static inline?

27

我发现了一个C函数的例子,声明如下:

static inline CGPoint SOCGPointAdd(const CGPoint a, const CGPoint b) {
    return CGPointMake(a.x + b.x, a.y + b.y);
}

直到现在,我将实用的 C 函数声明在 .h 文件中,并在 .m 文件中实现它们,就像这样:

CGPoint SOCGPointAdd(const CGPoint a, const CGPoint b) {
    return CGPointMake(a.x + b.x, a.y + b.y);
}

我可以在任何地方使用这个函数"inline",它也应该是"static",因为它与任何对象都没有关联,就像Objective-C方法一样。指定"static"和"inline"的目的/优点是什么?


这篇文章可能会回答你的问题:https://dev59.com/u3VC5IYBdhLWcg3w7Vtq - user376507
看一下这个答案(针对略有不同的问题)https://dev59.com/vWsz5IYBdhLWcg3wn5ba#7767858 - MByD
4个回答

61

inline并不意味着你可以使用“inline”函数(在其他函数中使用函数是正常的,你不需要inline); 它鼓励编译器将函数内联到使用它的代码中(通常旨在提高执行速度)。

static表示函数名称没有外部链接。如果未声明函数为static,编译器需要使其外部可见,以便与其他对象模块链接。为此,编译器必须包含一个单独的非内联函数实例。通过声明函数为static,你允许所有实例都可以内联到当前模块中,可能不留下单独的实例。

static inline通常用于小型函数,这种函数最好由调用程序完成,而不是使用调用机制,因为它们如此简短且快速,实际执行比调用单独的副本更好。例如:

static inline double square(double x) { return x*x; }

7
一个模块是一个编译单元:一个源文件加上它所包含的所有头文件。当您单独编译几个模块时,它们必须链接在一起成为一个程序。static的用法部分来自于该语言的历史。如果我们今天从零开始重新定义它,我们可能会称其为internal。涉及的概念称为linkage。 C中的标识符可以有三种链接类型:外部链接、内部链接和无链接。具有外部链接的标识符可以在多个模块中使用,并且因为它们被链接,标识符将引用... - Eric Postpischil
6
在不同的模块中,使用相同标识符指向同一对象。具有内部链接的标识符仅指向一个模块内的对象。如果您在另一个模块中使用相同的标识符,则它将指向一个不同的对象;它们没有链接以引用同一对象。(顺便说一下,在这里我所谓的模块是C标准所称的翻译单元。)具有内部链接的标识符可用于多个函数中以指代同一对象。然后还有没有链接的标识符。它们只能在声明它们的块内引用一个对象。 - Eric Postpischil
6
由于历史原因,确定连接标识符的规则有点复杂。大致来说,在文件作用域中声明的标识符默认具有外部链接性。添加“static”将其更改为内部链接性。在块作用域(函数内部)声明的标识符默认没有链接性。添加“static”也会将它们更改为内部链接性。这里有一个复杂之处:添加“extern”而不是“static”将块作用域中的声明更改为引用先前可见的声明,而不是没有链接性。该先前可见声明可能具有“static”,从而... - Eric Postpischil
5
一个用extern标记的声明指向一个具有内部链接的标识符。此外,static不仅会改变块中声明的标识符的链接,还会在对象创建和销毁时改变它们的存储类别。 - Eric Postpischil
3
更正:在块作用域内声明使用 static 的标识符仍然没有连接性,而非具有内部连接性。只有在文件作用域中使用 static 才会赋予内部连接性。 - Eric Postpischil
显示剩余8条评论

3
如果存储类为extern,则标识符具有外部链接,内联定义也提供外部定义。如果存储类为static,则标识符具有内部链接,并且内联定义在其他翻译单元中不可见。
通过声明函数为inline,您可以指示编译器将该函数的代码集成到其调用者的代码中(直接替换该函数的完整代码从被调用的地方),这样可以通过消除函数调用开销来加快执行速度。这就是为什么内联函数应该非常简短的原因。

3
我的 Obj-C 有点生疏,但我认为 C 静态函数与 Obj-C 类和静态 (类) 方法没多大关系,特别是这是一个函数不是方法? - Joe

3

在 C 语言中,inline 表示它是一个内联定义。它没有内部链接,也没有链接。它永远不会到达链接器,这意味着如果编译器没有使用内联定义来将每个对函数的引用都内联到编译单元中,那么如果同名符号(C 使用未加修饰的标识符)在编译中没有被另一个翻译单元导出并具有外部链接,则会出现本地链接器错误。由编译器实际内联对函数的引用完全由优化标志或 __attribute__((always_inline)) 控制。

static inlinestatic 之间没有区别,两者都不会内联函数,在 -O0 上将函数作为内部链接符号提供给链接器,并在 -O1 上内联和优化掉汇编输出中对函数的包含。 static inline 有一个特殊之处,即您可以在其之前使用非静态内联原型,但该原型将被忽略,并且不会用作前向声明(但在静态函数之前使用非静态原型是错误的)。

  • inline(GCC <5.0,默认使用-std=gnu90 / gnu89)/ extern inline(GCC 5.0及以后版本,默认使用-std=gnu11):这只是一个编译器内联定义。对于此内联定义,不会发生对外可见函数发射(在汇编输出中供汇编器和链接器使用)。如果文件中所有对该函数的引用实际上不是由编译器进行内联(并且内联发生在更高的优化级别或者你使用了__attribute__((always_inline)) inline float func()),则如果编译器不向链接器发出外部定义,则会导致本地链接错误(如果没有另一个翻译单元导出具有相同名称的外部链接符号)。这允许分别定义同一符号的内联定义和非内联函数,一个为内联,另一个为非内联,但不能在同一翻译单元中定义,否则编译器会将其视为重定义错误。内联定义仅对编译器可见,每个翻译单元可以拥有自己的定义。内联定义无法导出到其他文件,因为内联定义没有达到链接阶段。为了在编译时实现这一点,内联定义可以在头文件中,然后包含在每个翻译单元中。这意味着使用inline是一个编译器指令,而extern/static是指用于链接器生成的非内联版本。如果该函数在翻译单元中未定义,则它无法被内联,因为它留给链接器处理。如果函数已定义但不是内联的,则编译器将使用此版本(如果决定内联)。

  • extern inline(GCC <5.0)/ inline(GCC >5.0):对于此内联定义,无论是否进行内联,都会发射一个对外可见的函数,这意味着此限定符只能在翻译单元之一中使用。这直观地相反于“extern”。

  • static inline:对于此编译器内联定义,编译器将向汇编输出发射一个具有局部指示作为汇编器的函数。但是,在更高的优化级别上,如果所有函数都能够内联,则可能会进行优化。它永远不会导致链接错误。它与static的行为相同,因为编译器将与static inline一样在更高的优化级别上内联static定义。

  • 一个不是staticinline函数不应包含非const静态存储期变量或访问静态文件作用域变量,这会产生编译器警告。这是因为如果来自不同翻译单元的外部定义提供了非内联版本,则该函数的内联和非内联版本将具有不同的静态变量。编译器可能会内联一些函数,没有发射用于链接到这些引用的本地符号,并将链接留给链接器,后者可能会找到外部函数符号,因为它被假定与具有相同标识符的相同函数相同。因此,它提醒程序员应该逻辑上是const,因为修改并读取


"extern inline(从GCC 5.0开始,使用-std=gnu11)"。现在,这是一个GNU11的变化还是C11的变化? - dyp
@dyp 显然,这个改变发生在 c99 和 gnu99 中。 - Lewis Kelsey

1

内联函数用于在头文件中定义。小型函数在头文件中定义。应该将其声明为静态函数,以便只能访问静态成员。


3
如果我是一个没有斯坦福学位的祖母,你会如何向我解释这件事? - iamjustaprogrammer
9
我没有写那个答案,但我会建议你先阅读一本C语言入门书。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接