C语言 何时使用静态内联函数而不是常规函数

ffdz8vbo  于 2022-12-22  发布在  其他
关注(0)|答案(5)|浏览(207)

当我检查其他人的代码时,我有时会遇到头文件中实现的静态内联函数,而不是C文件中的常规函数实现。
例如,gitcache.h头文件(https://github.com/git/git/blob/master/cache.h)中就包含了很多这样的函数,下面复制其中一个;

static inline void copy_cache_entry(struct cache_entry *dst,
                    const struct cache_entry *src)
{
    unsigned int state = dst->ce_flags & CE_HASHED;

    /* Don't copy hash chain and name */
    memcpy(&dst->ce_stat_data, &src->ce_stat_data,
            offsetof(struct cache_entry, name) -
            offsetof(struct cache_entry, ce_stat_data));

    /* Restore the hash state */
    dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state;
}

我想知道与常规函数相比,使用静态内联函数的优点是什么。是否有任何准则可以用来选择采用哪种风格?

vsnjm48y

vsnjm48y1#

内联是为了优化,然而,一个鲜为人知的事实是inline也会损害性能:您的CPU有一个固定大小的指令缓存,而内联的缺点是在多个位置复制函数,这会降低指令缓存的效率。
因此,从性能的Angular 来看,通常不建议声明函数inline *,除非它们非常短,以至于调用开销比执行开销大 *。
把这一点联系起来:一个函数调用需要10到30个CPU周期的时间(取决于参数的数量)。2算术运算通常需要一个周期,然而,从一级缓存加载内存需要大约3到4个周期。3所以,如果你的函数比一个简单的序列(最多3次内存访问和一些算术运算)更复杂,那么内联它就没有什么意义了。
我通常采取这样的方法:

  • 如果一个函数只是增加一个计数器那么简单,并且它被广泛使用,我就内联它。这样的例子很少,但是引用计数是一个有效的例子。
  • 如果一个函数只在一个文件中使用,我将它声明为static,而不是inline,这样做的效果是编译器可以 * 看到 * 这样的函数被精确地使用了一次,如果它看到了,它很可能会内联它,不管它有多复杂,因为它可以证明内联没有缺点。
  • 所有其他函数既不是static,也不是inline

你问题中的例子是一个边缘性的例子:它包含了一个函数调用,因此乍一看,它对于内联来说似乎太复杂了。
但是,memcpy()函数是特殊的:它更多的被看作是语言的一部分而不是库函数。2大多数编译器会内联它,并且当它的大小是一个小的编译时间常数时会对其进行大量的优化,这就是我们讨论的代码的情况。
经过优化后,函数实际上被简化为一个简短的序列。我不能说它是否占用了大量内存,因为我不知道复制的结构。如果该结构很小,在这种情况下添加inline关键字似乎是个好主意。

pkbketx9

pkbketx92#

inline允许您在标题中定义函数。
static使函数仅在当前翻译单元中可用。

332nm8kg

332nm8kg3#

主要原因是性能:如果使用得当,内联函数可以使编译器生成更有效的代码。
识别性能瓶颈的一个好策略是分析代码。一旦这样做了,提高性能最有效的方法就是关注瓶颈。有很多策略,比如算法改进等。其中一个策略是缩短频繁使用的函数内联。
与任何其他改进性能的尝试一样,需要测试结果以确保更改确实有益。

wpx232ag

wpx232ag4#

内联的潜在优势在于,可以避免函数调用,这可以保存一些执行时间和堆栈内存,但会为可执行文件牺牲一些空间(如果函数被多次使用)。它还可以通过消除死代码进行进一步优化(例如,返回无效自变量的错误代码的函数,调用执行相同检查的函数,其中第二个相同检查可以在内联时移除)。注意,作为优化技术的内联和内联定义(如C标准所定义的)是两种不同的思想:编译器可以在它看到定义的地方内联每个函数,并且可以决定执行对inline函数的实际函数调用。
在严格符合的程序中,每个声明为static的函数都可以声明为inline,这只是对编译器的一个提示,没有任何语义意义(注意,对于有外部链接的函数,这是有区别的)。
有时,static inline被看作是宏函数的类型检查替代方法,因此可以被看作是用于某些文档目的。
将函数记录为static并在头文件中定义(或至少可能为static并在头文件中定义)是很重要的,因为这样的头文件的用户不能假设使用不同翻译单位的函数地址会产生相同的结果。
如果定义应该在头文件中(允许内联),我个人更喜欢使用带有外部链接的内联函数,因为地址比较相等,如果编译器认为值得的话,它仍然可以内联。

insrf1ej

insrf1ej5#

static inline函数在微控制器中使用特别安全。这些设备通常没有指令缓存(查看数据手册),因此内联总是节省时间。

相关问题