实现这一点的传统方法是将每个模块的源代码放在单独的目录中。每个目录都可以包含模块的所有源文件和头文件。
每个模块的公共头文件可以放置在一个单独的、公共的头文件目录中。对于每个标头,我可能会使用从公共目录到相关模块目录的符号链接。
编译规则简单地规定,除了公共目录中的头文件外,任何模块都不能包含来自其他模块的头文件。这实现了没有模块可以包含来自另一个模块的头的结果——除了公共头(从而强制执行私有屏障)。
自动防止循环依赖并非易事。问题是你只能通过一次查看几个源文件来确定存在循环依赖,而编译器一次只查看一个。
考虑一对模块 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...
这远非自动。
您可以对包含的文件进行分析,并要求存在无循环拓扑排序的依赖项。在 UNIX 系统上曾经有一个程序tsort(和一个配套程序lorder),它们一起提供所需的服务,以便.a可以创建一个包含目标文件的静态 () 库,其顺序不需要重新扫描存档。该ranlib程序最终ar承担ld了管理单个库的重新扫描的职责,因此lorder特别冗余。但tsort有更一般的用途;它在某些系统上可用(例如 MacOS X;RHEL 5 Linux 也是)。
因此,使用 GCC plus 的依赖跟踪tsort,您应该能够检查模块之间是否存在循环。但这必须小心处理。
可能有一些 IDE 或其他工具集可以自动处理这些东西。但通常情况下,只要仔细记录需求和模块间的依赖关系,程序员就可以受到足够的训练以避免出现问题。