实现这一点的传统方法是将每个模块的源代码放在单独的目录中。每个目录都可以包含模块的所有源文件和头文件。
每个模块的公共头文件可以放置在一个单独的、公共的头文件目录中。对于每个标头,我可能会使用从公共目录到相关模块目录的符号链接。
编译规则简单地规定,除了公共目录中的头文件外,任何模块都不能包含来自其他模块的头文件。这实现了没有模块可以包含来自另一个模块的头的结果——除了公共头(从而强制执行私有屏障)。
自动防止循环依赖并非易事。问题是你只能通过一次查看几个源文件来确定存在循环依赖,而编译器一次只查看一个。
考虑一对模块 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 或其他工具集可以自动处理这些东西。但通常情况下,只要仔细记录需求和模块间的依赖关系,程序员就可以受到足够的训练以避免出现问题。