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

时间:2010-10-18 04:56:56

标签: c static header code-organization

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

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

谢谢

6 个答案:

答案 0 :(得分:25)

一些想法:

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

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

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

有人说过......

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

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

答案 1 :(得分:9)

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

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

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

答案 2 :(得分:8)

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:-1)

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

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

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