实现这一目标的传统方法是将每个模块的源代码放入单独的目录中。每个目录可以包含模块的所有源文件和头文件。
每个模块的公共标头可以放置在单独的公共标头目录中。我可能会为每个标头使用从公共目录到相关模块目录的符号链接。
编译规则只是规定,除了公共目录中的标头之外,任何模块都不能包含来自其他模块的标头。这实现了任何模块都不能包含来自另一个模块的标头 - 除了公共标头(从而强制执行私有屏障)。
自动防止循环依赖并非易事。问题在于,您只能通过一次查看多个源文件来确定是否存在循环依赖关系,而编译器一次只查看一个。
考虑一对模块(ModuleA 和 ModuleB)以及一个使用这两个模块的程序(Program1)。
base/include
ModuleA.h
ModuleB.h
base/ModuleA
ModuleA.h
ModuleA1.c
ModuleA2.c
base/ModuleB
ModuleB.h
ModuleB1.c
ModuleB2.c
base/Program1
Program1.c
编译 Program1.c 时,如果它使用 ModuleA.h 和 ModuleB.h 的服务,则完全合法地包含这两个模块的服务。因此,如果 ModuleB.h 包含在同一翻译单元 (TU) 中,则 ModuleA.h 不能抱怨,如果 ModuleA.h 包含在同一 TU 中,ModuleB.h 也不能抱怨。
让我们假设 ModuleA 使用 ModuleB 的设施是合法的。因此,在编译 ModuleA1.c 或 ModuleA2.c 时,同时包含 ModuleA.h 和 ModuleB.h 不会有问题。
但是,为了防止循环依赖,您必须能够禁止 ModuleB1.c 和 ModuleB2.c 中的代码使用 ModuleA.h。
据我所知,执行此操作的唯一方法是某种技术,该技术需要 ModuleB 的私有标头,该标头显示“ModuleA 已包含”,即使它没有包含在内,并且这是在包含 ModuleA.h 之前包含的。
ModuleA.h 的骨架将是标准格式(ModuleB.h 也将类似):
#ifndef MODULEA_H_INCLUDED
#define MODULEA_H_INCLUDED
...contents of ModuleA.h...
#endif
现在,如果 ModuleB1.c 中的代码包含:
#define MODULEA_H_INCLUDED
#include "ModuleB.h"
...if ModuleA.h is also included, it will declare nothing...
...so anything that depends on its contents will fail to compile...
这远非自动的。
您可以对包含的文件进行分析,并要求存在依赖关系的无循环拓扑排序。曾经有一个节目tsort
在 UNIX 系统上(以及一个配套程序,lorder
)共同提供所需的服务,以便静态(.a
)可以按顺序创建包含目标文件的库,不需要重新扫描存档。这ranlib
程序,最终ar
and ld
承担了管理单个图书馆重新扫描的职责,从而使lorder
特别多余。但tsort
具有更通用的用途;它在某些系统上可用(例如 MacOS X;RHEL 5 Linux)。
因此,使用 GCC plus 的依赖跟踪tsort
,您应该能够检查模块之间是否存在循环。但这必须小心处理。
可能有一些 IDE 或其他工具集可以自动处理这些事情。但通常情况下,只要仔细记录需求和模块间依赖关系,程序员就可以遵守足够的纪律来避免出现问题。