这是一个好习惯吗?
TLDR;没有。
背景:
在过去的一年半里,我几乎只在 STM32 微控制器上使用嵌入式 C 进行编程(与使用 C++ 或“C+”相反,如下所述)。像我一样,必须在 架构级别 学习 C,这对我来说非常有见地。我已经非常努力地研究了 C 架构,以达到我可以说我“知道 C”的地方。事实证明,众所周知,C 和 C++ 不是同一种语言。在语法级别,C 几乎完全是 C++ 的一个子集(在 C 支持 C++ 不支持的东西的地方有一些关键差异),因此为什么人们(包括我自己在内)经常认为/认为它们几乎是相同的语言,但在建筑水平他们是非常不同的动物。
旁白:
请注意,我最喜欢的嵌入式方法是使用一些俗称的“C+”。它基本上是使用 C++ 编译器来编写 C 风格的嵌入式代码。您基本上只是按照您希望编写 C 的方式编写 C,除了您使用 C++ 类来极大地简化(否则为纯 C)体系结构。换句话说,“C+”是一个笔名,用于描述使用 C++ 编译器编写类 C 代码,使用类而不是“基于对象的 C”架构(如下所述)。您有时也可能使用一些高级 C++ 概念,例如运算符重载或模板,但在大多数情况下避免使用 STL,以免在初始化后意外使用动态分配(例如,在幕后和自动,如 C++ 向量所做的那样), 因为在正常运行时动态内存分配/释放会很快耗尽稀缺的 RAM 资源,并使原本确定性的代码变得不确定。所谓的“C+”还可能包括混合使用 C(使用 C 编译器编译)和 C++(使用 C++ 编译器编译),根据需要链接在一起(不要忘记您在包含的 C 头文件中使用 extern "C"在您的 C++ 代码中,根据需要)。
核心 Arduino 源代码(同样,core,不一定是他们的示例“草图”或初学者示例代码)在这方面做得非常好,可以用作优秀“C+”的模型“ 设计。
[旁观]
架构 C 笔记:
所以,关于 C 架构(即:实际的 C,而不是“C+”/C 风格的 C++):
如您所知,C 不是一种面向对象的语言,但它可以以“基于对象”的风格编写。请注意,我说的是“基于对象”,而不是“面向对象”,因为这就是我听到其他迂腐 C 程序员提到它的方式。我可以说我编写了基于对象的 C 架构,它实际上非常有趣。
要创建基于对象的 C 架构,需要记住以下几点:
- 命名空间可以在 C 中通过在命名空间名称前面加上下划线来完成。毕竟,这就是一个名称空间。例如:
mylibraryname_foo()、mylibraryname_bar() 等。例如,将其应用于枚举,因为 C 没有像 C++ 那样的“枚举类”。也将它应用于所有 C 类“方法”,因为 C 没有类。适用于与特定库相关的所有全局变量或定义。
- 在创建 C“类”时,您有 2 个主要的体系结构选项,它们都非常有效且被广泛使用:
- 使用public structs(可能隐藏在名为“myheader_private.h”的标头中,给他们一种伪隐私感)
- 使用不透明结构(通常称为“不透明指针”,因为它们是指向不透明结构的指针)
- 在创建 C“类”时,您可以选择将指向函数的指针封装在上面的结构中,以使其具有更“C++”的感觉。这有点常见,但在我看来,这是一个可怕的想法,它使代码几乎无法遵循并且非常难以阅读、理解和维护。
第一个选项,公共结构:
制作一个包含所有“类数据”的结构定义的头文件。我建议您不要包含指向函数的指针(稍后将讨论)。 这实质上为您提供了一个“所有成员都是公共的 C++ 类”的等价物。 缺点是您不会隐藏数据。好处是您可以使用所有 C“类对象”的静态内存分配,因为包含这些库头文件的用户代码知道结构的完整规范和大小。
第二个选项:不透明结构:
在您的库头文件中,对结构进行前向声明:
/// Opaque pointer (handle) to C-style "object" of "class" type mylibrarymodule:
typedef struct mylibrarymodule_s *mylibrarymodule_h;
在您的库 .c 源文件中,提供 struct mylibrarymodule_s 的完整定义。由于这个库的用户只包含头文件,他们看不到这个不透明结构的完整实现或大小。这就是“不透明”的意思:“隐藏”。它被混淆或隐藏起来。 这基本上为您提供了“所有成员都是私有的 C++ 类”的等价物。 好处是您可以获得真正的数据隐藏。缺点是你不能使用这个库在你的用户代码中为你的任何C“类对象”使用静态内存分配,因为包括这个库的任何用户代码甚至都不知道结构有多大,所以它不能是静态的分配。相反,库必须在程序初始化时进行一次动态内存分配,即使对于嵌入式确定性实时安全关键系统也是安全的,因为您在正常程序执行期间不会分配或释放内存。
就个人而言,我认为具有静态内存分配和“所有公共成员”的选项 1 可能是我的首选方法,但我最熟悉 opaque struct 选项 2 方法,因为这就是我工作的 C 代码库用途最多。
上面的第 3 条:在结构中包含指向函数的指针。
这是可以做到的,有些人做到了,但我真的很讨厌。不要这样做。它只会让你的代码很难理解。例如,在具有出色索引器的 Eclipse 中,我可以 Ctrl + 单击任何内容,它会跳转到它的定义。如果我想查看我在 C“对象”上调用的函数的实现怎么办?我 Ctrl + 单击它,它会跳转到 pointer 函数的声明。但是功能在哪里???我不知道!我可能需要 10 分钟 grepping 并使用查找或搜索工具,在代码库中挖掘所有内容,才能找到臭名昭著的函数定义。一旦找到它,我就会忘记我在哪里,并且每次使用这种方法编辑库模块时,我都必须为每个函数重新重复一遍。这很糟糕。上面的不透明指针方法效果很好,公共指针方法也很容易。
现在,直接回答您的问题:
为了使库使用者更容易对这些结构实例进行通用操作,我可以在结构本身内提供指向这些函数的函数指针吗?
是的,你可以,但它只会让调用更容易。不要这样做。找到函数来查看它的实现变得非常困难。
这是一个好习惯吗?
不,请改用上面的选项 1 或选项 2,您现在只需在每个 C“对象”上调用 C“命名空间”“方法”。您必须简单地将“C 类的成员”作为每个调用的第一个参数传递给函数。这意味着您可以在 C++ 中执行以下操作:
myclass.dosomething(int a, int b);
你只需要在基于对象的 C 中做:
// Notice that you must pass the "guts", or member data
// (`mylibrarymodule` here), of each C "class" into the namespaced
// "methods" to operate on said C "class object"!
// - Essentially you're passing around the guts (member variables)
// of the C "class" (which guts are frequently referred to as
// "private data", or just `priv` in C lingo) to each function that
// needs to operate on a C object
mylibrarymodule_dosomething(mylibrarymodule_h mylibrarymodule, int a, int b);
在多线程中是否存在与不同参数并行调用实用函数等的问题?
是的,与任何多线程尝试访问相同数据的多线程情况相同。只需将互斥锁添加到每个基于 C 结构的“对象”,并确保作用于 C“对象”的每个“方法”正确锁定(获取)和解锁(提供)互斥锁,然后再对C“对象”。
相关:
-
Opaque C structs: how should they be declared? [使用“基于对象”的 C 架构]