13

随着项目规模的扩大,您知道哪些方法、实践和约定可以模块化 C 代码?

4

8 回答 8

16

创建仅包含使用模块所需内容的头文件。在相应的 .c 文件中,将任何不应该在外部可见的内容(例如辅助函数)设为静态。在所有外部可见的名称上使用前缀有助于避免命名空间冲突。(如果一个模块跨越多个文件,事情会变得更难。因为你可能需要暴露内部的东西,而不能用“静态”隐藏它们)

(如果我要尝试改进 C,我会做的一件事是将“静态”作为函数的默认范围。如果您想要在外部可见的东西,则必须用“导出”或“全局”或其他东西标记它相似的。)

于 2009-07-22T03:22:35.747 回答
11

OO 技术可以应用于 C 代码,它们只是需要更多的纪律。

  • 使用不透明的句柄对对象进行操作。库是如何做到这一点的一个很好的例子stdio——一切都围绕着不透明的FILE*句柄组织起来。许多成功的库都是围绕这一原则组织的(例如zlibapr
  • 因为 s 的所有成员struct都隐含public在 C 中,所以您需要一个约定 + 程序员纪律来实施有用的信息隐藏技术。选择一个简单的、可自动检查的约定,例如“私有成员以 '_' 结尾”。
  • 可以使用指向函数的指针数组来实现接口。当然,与提供语言内支持的 C++ 等语言相比,这需要更多的工作,但它仍然可以在 C 中完成。
于 2009-07-22T03:24:39.580 回答
3

High and Low-Level C文章包含很多很好的技巧。特别是,看看“类和对象”部分。

ANSI C 中编码的标准和风格也包含您可以挑选的好建议。

于 2009-07-22T03:25:06.523 回答
3
  1. 不要在头文件中定义变量;相反,在源文件中定义变量并在标题中添加一个 extern 语句(声明)。这将与#2 和#3 相关联。
  2. 在每个标题上使用包含保护。这将省去很多麻烦。
  3. 假设您已完成 #1 和 #2,请在该文件中包含某个文件所需的所有内容(但仅包含您需要的内容)。不要依赖于编译器扩展包含指令的顺序。
于 2009-07-22T03:34:37.890 回答
2

一个函数应该做一件事,并且把这件事做好。

较大的包装函数使用的许多小函数有助于从小的、易于理解(和测试!)的构建块构建代码。

创建每个具有几个功能的小模块。只公开你必须做的,在模块内保持其他任何东西都是静态的。将小模块与其 .h 接口文件链接在一起。

提供 Getter 和 Setter 函数以访问模块中的静态文件范围变量。这样,变量实际上只被写入一个地方。这也有助于使用函数和调用堆栈中的断点来跟踪对这些静态变量的访问。

设计模块化代码时的一个重要规则是:除非必须,否则不要尝试优化。许多小函数通常会产生更清晰、结构良好的代码,并且额外的函数调用开销可能是值得的。

我总是试图将变量保持在最窄的范围内,也在函数内。例如,for 循环的索引通常可以保留在块范围内,不需要在整个函数级别公开。C 不像 C++ 那样灵活,“在你使用它的地方定义它”,但它是可行的。

于 2009-07-22T04:49:09.957 回答
2

Pidgin(以前的 Gaim)使用的方法是他们创建了一个Plugin结构。每个插件都使用初始化和拆卸的回调填充一个结构,以及一堆其他描述性信息。除了结构体之外,几乎所有的东西都被声明为静态的,所以只有 Plugin 结构体被暴露用于链接。

然后,为了处理插件与应用程序其余部分通信的松散耦合(因为如果它在设置和拆卸之间做一些事情会很好),他们有一个信号系统。插件可以注册回调以在应用程序的任何部分(包括另一个插件)发出特定信号(不是标准 C 信号,而是自定义可扩展类型 [由字符串标识,而不是设置代码])发出时调用。他们也可以自己发出信号。

这在实践中似乎运作良好 - 不同的插件可以相互构建,但耦合相当松散 - 没有直接调用函数,一切都通过信号系统。

于 2009-07-22T03:24:53.247 回答
1

将代码分解为相关函数的库是保持事物井井有条的一种方法。为了避免名称冲突,您还可以使用前缀来允许您重用函数名称,尽管使用好的名称我从来没有真正发现这是一个很大的问题。例如,如果您想开发自己的数学例程,但仍使用标准数学库中的一些例程,则可以在您的例程前面加上一些字符串:xyz_sin()、xyz_cos()。

一般来说,我更喜欢每个文件一个函数(或一组密切相关的函数)和每个源文件约定一个头文件。将文件分成目录,每个目录代表一个单独的库也是一个好主意。您通常会有一个 makefile 或构建文件系统,允许您按照代表各种库/程序的层次结构构建整个系统的全部或部分。

于 2009-07-22T03:21:13.707 回答
0

有目录和文件,但没有命名空间或封装。您可以将每个模块编译为单独的 obj 文件,并将它们链接在一起(作为库)。

于 2009-07-22T03:16:56.927 回答