C代码模块化有哪些方法?
随着项目规模的扩大,您知道哪些方法、实践和约定可以模块化 C 代码?
What methods, practices and conventions do you know of to modularize C code as a project grows in size?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
创建仅包含使用模块所需内容的头文件。 在相应的 .c 文件中,将任何不应该在外部可见的内容(例如辅助函数)设为静态。 对外部可见的所有内容的名称使用前缀有助于避免命名空间冲突。 (如果一个模块跨越多个文件,事情会变得更困难,因为你可能需要暴露内部的东西并且不能用“static”隐藏它们)
(如果我要尝试改进C,我会做的一件事就是使“ static”函数的默认作用域。如果您想要外部可见的内容,则必须使用“export”或“global”或类似的内容来标记它。)
Create header files which contain ONLY what is necessary to use a module. In the corresponding .c file(s), make anything not meant to be visible outside (e.g. helper functions) static. Use prefixes on the names of everything externally visible to help avoid namespace collisions. (If a module spans multiple files, things become harder., as you may need to expose internal things and not be able hide them with "static")
(If I were to try to improve C, one thing I would do is make "static" the default scoping of functions. If you wanted something visible outside, you'd have to mark it with "export" or "global" or something similar.)
OO 技术可以应用于 C 代码,只是需要更多的纪律。
stdio
库就是如何完成此操作的一个很好的例子 - 所有内容都是围绕不透明的FILE*
句柄组织的。 许多成功的库都是围绕这一原则组织的(例如 zlib、apr)struct
的所有成员在 C 中都是隐式public
的,所以你需要一个约定 + 程序员纪律来实施有用的信息隐藏技术。 选择一个简单的、可自动检查的约定,例如“私有成员以‘_’结尾”。OO techniques can be applied to C code, they just require more discipline.
stdio
library -- everything is organised around the opaqueFILE*
handle. Many successful libraries are organised around this principle (e.g. zlib, apr)struct
s are implicitlypublic
in C, you need a convention + programmer discipline to enforce the useful technique of information hiding. Pick a simple, automatically checkable convention such as "private members end with '_'".高级和低级 C 文章包含很多很好的技巧。 特别是,请查看“类和对象”部分。
ANSI C 编码标准和风格 还包含一些很好的建议,您可以挑选。
The High and Low-Level C article contains a lot of good tips. Especially, take a look at the "Classes and objects" section.
Standards and Style for Coding in ANSI C also contains good advice of which you can pick and choose.
Pidgin(以前称为 Gaim)使用的方法是创建一个
Plugin
结构。 每个插件都会使用初始化和拆卸的回调以及一堆其他描述性信息填充一个结构。 除了结构之外,几乎所有内容都被声明为静态,因此只有 Plugin 结构公开用于链接。然后,为了处理插件与应用程序其余部分通信的松散耦合(因为如果它在安装和拆卸之间执行某些操作会很好),他们有一个信号系统。 插件可以注册当应用程序的任何部分(包括另一个插件)发出特定信号(不是标准 C 信号,而是自定义可扩展类型[由字符串而不是设置代码标识])时调用的回调。 他们也可以自己发出信号。
这在实践中似乎运作良好 - 不同的插件可以相互构建,但耦合相当松散 - 没有直接调用函数,一切都通过信号系统进行。
The approach that Pidgin (formerly Gaim) uses is they created a
Plugin
struct. Each plugin populates a struct with callbacks for initialization and teardown, along with a bunch of other descriptive information. Pretty much everything except the struct is declared as static, so only the Plugin struct is exposed for linking.Then, to handle loose coupling of the plugin communicating with the rest of the app (since it'd be nice if it did something between setup and teardown), they have a signaling system. Plugins can register callbacks to be called when specific signals (not standard C signals, but a custom extensible kind [identified by string, rather than set codes]) are issued by any part of the app (including another plugin). They can also issue signals themselves.
This seems to work well in practice - different plugins can build upon each other, but the coupling is fairly loose - no direct invocation of functions, everything's through the signaling stystem.
一个函数应该做一件事,并且把这一件事做好。
较大的包装函数使用的许多小函数有助于从小的、易于理解(和测试!)的构建块构建代码。
创建每个具有几个功能的小模块。 只公开你必须公开的内容,将模块内的其他内容保持静态。 将小模块与其 .h 接口文件链接在一起。
提供 Getter 和 Setter 函数来访问模块中的静态文件范围变量。 这样,变量实际上只写入一个地方。 这还有助于使用函数和调用堆栈中的断点来跟踪对这些静态变量的访问。
设计模块化代码时的一条重要规则是:除非必要,否则不要尝试优化。 许多小函数通常会产生更干净、结构良好的代码,并且额外的函数调用开销可能是值得的。
我总是尝试将变量保持在最窄的范围内,也在函数内。 例如,for 循环的索引通常可以保留在块范围内,不需要在整个函数级别公开。 C 不像 C++ 那样灵活,可以“在使用的地方定义它”,但它是可行的。
A function should do one thing and do this one thing well.
Lots of little function used by bigger wrapper functions help to structure code from small, easy to understand (and test!) building blocks.
Create small modules with a couple of functions each. Only expose what you must, keep anything else static inside of the module. Link small modules together with their .h interface files.
Provide Getter and Setter functions for access to static file scope variables in your module. That way, the variables are only actually written to in one place. This helps also tracing access to these static variables using a breakpoint in the function and the call stack.
One important rule when designing modular code is: Don't try to optimize unless you have to. Lots of small functions usually yield cleaner, well structured code and the additional function call overhead might be worth it.
I always try to keep variables at their narrowest scope, also within functions. For example, indices of for loops usually can be kept at block scope and don't need to be exposed at the entire function level. C is not as flexible as C++ with the "define it where you use it" but it's workable.
将代码分解为相关函数的库是保持事物井井有条的一种方法。 为了避免名称冲突,您还可以使用前缀来允许重用函数名称,尽管对于好的名称,我从未真正发现这是一个大问题。 例如,如果您想开发自己的数学例程,但仍使用标准数学库中的一些例程,则可以使用一些字符串作为前缀:xyz_sin()、xyz_cos()。
一般来说,我更喜欢每个文件一个函数(或一组密切相关的函数)和每个源文件约定一个头文件。 将文件分成目录,其中每个目录代表一个单独的库也是一个好主意。 您通常会有一个 makefile 或构建文件系统,允许您按照代表各种库/程序的层次结构构建整个系统的全部或部分。
Breaking the code up into libraries of related functions is one way of keeping things organized. To avoid name conflicts you can also use prefixes to allow you to reuse function names, though with good names I've never really found this to be much of a problem. For example, if you wanted to develop your own math routines but still use some from the standard math library, you could prefix yours with some string: xyz_sin(), xyz_cos().
Generally I prefer the one function (or set of closely related functions) per file and one header file per source file convention. Breaking files into directories, where each directory represents a separate library is also a good idea. You'd generally have a system of makefiles or build files that would allow you to build all or part of the entire system following the hierarchy representing the various libraries/programs.
有目录和文件,但没有命名空间或封装。 您可以将每个模块编译为单独的 obj 文件,并将它们链接在一起(作为库)。
There are directories and files, but no namespaces or encapsulation. You can compile each module to a separate obj file, and link them together (as libraries).