【问题标题】:Is it bad practice to declare a C function as static if it can still be executed indirectly (through a callback function)?如果仍然可以间接执行(通过回调函数),将 C 函数声明为静态函数是不好的做法吗?
【发布时间】:2020-06-09 04:03:40
【问题描述】:

我有一个用于嵌入式系统的 C 模块(foo.cfoo.h),它包含一个函数 my_driver_fn(),从 API 的角度来看,它在范围内是本地的(例如,不在 foo 的公共标头中:任何其他通过#include "foo.h" 使用其 API 的代码不应被允许调用此函数)。假设 my_driver_fn() 是可重入的。

但是,foo 使用库 libdostuff 需要使用一些用户提供的回调函数(架构/硬件特定的东西)进行初始化,才能在任何平台上正常工作。在foo 中,上面提到的my_driver_fn 将是有问题的功能之一……libdostuff 需要,但任何使用foo 的人都不需要。

对于这些回调函数(my_driver_fn() 在@987654335 中声明为static @? 鉴于它的地址是提供给libdostuff 并且它是“间接”调用的(尽管从未直接调用)?

注意:我碰巧同时写了foolibdostuff,我想知道用户提供的函数是extern 并在链接时纯粹解决,还是通过通过初始化函数中提供的用户提供的回调表进入libdostuff(例如libdostuff_init(CallbackTable *user_callbacks),其中CallbackTable有一个函数指针,该指针将被初始化为指向my_driver_fn

【问题讨论】:

  • @M.M 从封装的角度我同意。我只是想确保我根本没有妨碍编译器,或者调用未定义的行为(编译器假设它不能在模块外部调用,所以一些恶作剧也是如此)
  • @M.M 请提交答案,我可以给你点赞
  • 你为什么要标记这个 C++ btw?这是另一个故事——你可以使用私有成员函数,而静态成员可能不是最好的主意。 C++ 总体上不适合嵌入式系统 - 太多的地雷会炸掉你的整条腿。

标签: c++ c linker embedded compiler-optimization


【解决方案1】:

在我看来,这是一个很好的做法。 static 指的是名称的可见性,仅此而已。

如果其他翻译单元不需要使用该名称,则将其标记为static 可降低外部可见函数的“命名空间”发生冲突的风险。

【讨论】:

  • 知道了。我只是担心将其标记为静态会影响链接时间优化或引入副作用。听起来不像
  • @Brett 如果您将函数的地址放在任何地方,编译器应该会注意到。如果编译器没有意识到函数被调用,它可能需要一些其他的指示。 ,我听说过,不过我猜你到了那座桥就可以过桥了。
  • 要详细说明“应该注意”,如果编译器不能证明静态函数不能从它看不到的代码中访问,如果不能进行任何如果从它看不到的代码中获得的转换将是无效的。
  • @R..GitHubSTOPHELPINGICE 如果符合要求,当然 :) 虽然在构建静态库和决定要包含的内容时,这可能是灰色的。
  • @M.M:我不关注。静态库在这里没有问题。
【解决方案2】:

这是一种很好的做法,没有不良定义行为等负面影响。

使用static 的原因有很多,例如减少命名空间污染和避免意外/有意调用。但主要原因实际上是私有封装设计和自文档化代码——将函数保留在它们所属的模块中,这样外界就不用担心如何以及何时调用它们。他们应该只关心你在公共头文件中声明的外部链接函数。

这正是您在嵌入式系统中设计 ISR 的方式。它们应始终声明为static,并放置在控制与 ISR 相关的硬件的驱动程序中。与 ISR 的所有通信,包括竞争条件保护,都应封装在该驱动程序中。调用应用程序从不直接与 ISR 对话,不需要担心重入等问题。DMA 缓冲区也是如此。

作为一个例子,我总是为我的所有 MCU 项目设计一个循环定时器驱动程序,其中调用者的 API 允许他们注册简单的回调函数。然后,计时器驱动程序在计时器经过时从 ISR 内部调用回调。然后,这可以用作所有需要计时器的低优先级任务的通用计时器:延迟,去弹跳等。然后调用者只需要负责任地保持回调函数最小并以毫秒为单位指定时间,但调用者不知道也不关心定时器硬件,定时器硬件对它执行的回调一无所知。两个方向的松散耦合。此计时器 API 也可用作 HAL,因此您可以将代码移植到另一个 MCU,而无需更改调用者代码 - 只需更改底层驱动程序。

【讨论】:

  • 虽然我想,一个小缺点是你会阻止调用方的内联,除非你通过头文件公开inline 函数定义。 (当然,您仍然可以始终将它内联在它所属的模块中。)但这是一个非常罕见的情况 - 我很少遇到我实际上需要这样做以提高性能的情况,然后我几乎完全使用硬实时系统。
【解决方案3】:

这是的做法。它确保该函数只能由您明确提供回调的代码调用。如果它有外部链接,任何东西都可以调用它,不管它是否有意义。很明显,滥用并非不可能,但确实会让意外变得更加困难。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-22
    • 2013-11-19
    • 2021-01-05
    • 1970-01-01
    • 2011-02-28
    • 2013-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多