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

Posted

技术标签:

【中文标题】何时将静态函数定义放在 C 的头文件中?【英文标题】:When to put static function definitions in header files in C? 【发布时间】:2011-04-26 18:45:47 【问题描述】:

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

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

谢谢

【问题讨论】:

我只在头文件中使用静态函数来编程简单的微控制器。在这种环境下,只有一个程序在芯片上运行(没有操作系统),并且包含的​​功能也往往非常简单,例如通过串口发送数据。我不知道这是否是正确的答案(如果不是,我很想听听一些反馈),但它为我简化了事情,我看不出这个特定应用程序有任何缺点。 【参考方案1】:

一些想法:

我能想到的一种可能的合法用途是,当您希望在不创建具有外部链接的符号和污染外部命名空间的情况下使函数可用时。 (但是你可以在头文件中使用像mylib123__foobar#define foobar mylib123__foobar 这样的晦涩前缀名称,所以这个看起来有点不确定。) 您希望某些功能仅通过头文件可用,而不需要用户链接库/目标文件。当提供一个几乎只有数据结构和一些简单的代码来操作它们的“库”时,我可以看到这是一个真正的动机。事实上,如果数据结构不是不透明的并且意味着可以由应用程序直接访问,那么将与它们一起使用的函数放在同一个头文件中(相对于库中)可以大大降低如果/当您更改数据时破坏事物的风险结构。

也许函数只是外部函数的包装器,包装器的工作方式可能取决于调用编译单元中的编译时选项。例如:

static int foobar(int x)

    return real_foobar(COMPILETIME_PARAMETER, x);

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

说了这么多……

实际上,人们将static 函数放在头文件中的主要原因通常是基于一些 10 年前的过时概念,即通过允许编译器内联函数或诸如此类来提高性能。大多数这样做的人都没有做过任何测量。由于现代编译器可以根据要求将整个程序编译为一个单元,并且理论上这会带来更多优化的可能性,并且由于它从一开始就是一个有问题的优化,我真的很怀疑将函数放置在头文件中以达到性能目的.

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

【讨论】:

【参考方案2】:

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

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

简而言之,除非您毫无疑问地知道您需要在头文件中使用静态函数...您不希望在头文件中使用静态函数;您希望 .c 文件中有一个非静态函数,其标头在 .h 中。

【讨论】:

【参考方案3】:

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

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

【讨论】:

我认为你的第二段是正确的。我确实同意,如果您可以使函数可重入(例如,通过分配然后传入指向状态结构的指针),那将是一个更好的解决方案。【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

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

在头文件中定义函数是绝对不行的。

【讨论】:

他说的是定义而不是声明。那就是函数的主体在标题中 我不同意“永远不行”的部分。这可能不是一个理想的设计,但正如 tobyodavies 所说,这是有原因的。

以上是关于何时将静态函数定义放在 C 的头文件中?的主要内容,如果未能解决你的问题,请参考以下文章

c++ 全局静态函数的理解

c语言中的头文件

函数实现不放在头文件的原因,及何时可以放头文件的情况

c语言怎么包含自己写的头文件?

如何在Python中定义静态变量

c语言练习题