何时将静态函数定义放在C中的头文件中?

我遇到了一些在头文件中有一个大的静态函数的代码,我只是好奇它什么时候/它不好。 例如,如果许多.c文件包含标题,为什么不直接定义非静态函数并将其链接?

关于何时/何时不将静态函数定义放在C中的头文件中的任何建议或经验法则,我们将不胜感激,

谢谢

一些想法:

  • 我能想到的一个可能的合法用途是,当您想要创建一个函数而不创建带有外部链接的符号并污染外部名称空间时。 (但是你可以在头文件中使用一个模糊的前缀名称,如mylib123__foobar#define foobar mylib123__foobar ,所以这个看起来有点不合适。)
  • 您希望某些function仅通过头文件可用,而无需用户链接库/目标文件。 我可以看到这是一个真正的动机,提供一个几乎只有数据结构和操作它们的一些琐碎代码的“库”。 实际上,如果数据结构不是不透明的并且意图由应用程序直接访问,那么将函数与它们一起使用在同一个头文件中(与库中相比)可以大大降低在更改数据时破坏事物的风险结构。
  • 也许该函数只是外部函数的包装器,包装器的工作方式可能取决于调用编译单元中的编译时选项。 例如:

     static int foobar(int x) { return real_foobar(COMPILETIME_PARAMETER, x); } 

    您可能会说只使用宏,但是如果需要通过函数指针调用foobar来实现预期用途呢?

有了这样说……

实际上,人们将static函数放在头文件中的主要原因通常是基于一些10年过时的概念,它会通过允许编译器内联函数或诸如此类来提高性能。 大多数这样做的人都没有做过任何测量。 由于现代编译器可以将整个程序编译成一个单元,如果被问到,这理论上会导致更多的优化可能性,并且因为它开始时是一个值得怀疑的优化,所以我真的对为了性能目的而在函数头中放置函数持怀疑态度。

这种批评特别适用于OP在头文件中的“大”静态函数的例子。 除非常量参数值允许编译器消除90%的代码或其他内容,否则大型函数几乎无法从内联中受益。 (有关这种极端情况的真实示例,请参阅libavcodec使用的一些疯狂的内联函数/宏定义。:-)

根据经验,您不应该在头文件中放置静态函数。 在一次性程序中,除了扩展代码的大小之外,它可能不会伤害任何东西,因为你在每个模块中都有一个冗余副本。 在共享库中,它很容易导致错误,因为现在您的库的一部分嵌入在库的调用者中,因此很容易发生版本不匹配。

如果你有一个非常可怕的时间关键函数,其中使函数调用花费的时间很重要,你可以考虑将它放在标题中,但在这种情况下(a)你可能也想要将它声明为内联函数,并且(b)您已经完成了可以找到的所有其他优化。

简而言之,除非你知道在头文件中需要静态函数,否则你不需要在头文件中使用静态函数; 你想要一个.c文件中的非静态函数,其标题在.h中。

根据我的经验,在.h文件中定义一个函数通常是一个坏主意,而且我从来没有理由这样做,偶然这样做会导致我头痛不已。

虽然我猜它会允许包含标题的每个文件都有自己独立的函数实现,如果函数有静态变量,可能是所需的行为,例如,如果你想/需要分别跟踪每个信息的某些信息文件。

将静态工作缓冲区定义为每个转换单元本地的函数也很有用。 一个特定的例子是strtok()。 strtok()通过缓冲区每次调用一个令牌。 如果strtok()调用从两个不同的位置(即两个不同的转换单元)交错,则结果不是预期/期望的结果。 如果每个翻译单元都有自己的strtok()副本,因此每个翻译单元都有自己的strtok()静态变量,那么这种对内部状态的踩踏就会消失。 如果发生状态踩踏,则两个(组)调用都在同一个转换单元中,并且调试具有一些相似的局部性。

(注意,“正确”的解决方案是用无状态函数替换strtok()并使调用者负责保存上下文和状态信息,就像fopen()和朋友让调用者为每个上下文保持一个FILE一样。)

Modern C采用了C ++中的inline关键字来完成这样的任务。 但是,如果您的编译器没有(但是?)头文件中的static是一种模拟它的方法。 inline并不意味着函数必须内联到任何调用者,而只是在最终可执行文件中通常最多只有一个副本。 (从技术上讲,相应的链接符号是“弱”符号。)相反,如果只是声明static每个编译单元都会保留一个副本。

这种在头文件中具有函数定义的方法应限于执行小任务的函数,如果编译器优化到调用函数中,编译器可能会大大改善代码。

这样做时,还要小心这些function的实现。 您可能会破坏将声明包含在C ++中的可能性。 通常,这两种语言(大多数)只在接口上达成一致,不一定是为了实现,存在细微差别。

如果函数具有外部链接,则应在.h文件中声明。

如果函数是静态的,因此没有外部链接,则该函数只应在定义它的.c文件中声明。

在头文件中定义函数永远都不行。