做者 Alex Blewitt ,译者 臧秀涛 发布于 十二月 05, 2012
程序员
在今年11月的LLVM开发者大会上,来自Apple的Doug Gregor作了一场讲座,主题是向C语言中加入模块(Module)机制。讲座中提到:数据结构
长期以来,C的预处理器就是程序员和工具的问题之源。写得很差的头文件导致宏污染和包含顺序等问题大量存在,程序员必须不断地与之斗争。为了缓解这些问题,开发者习惯上采用各类预处理器变通方案,好比LONG_MACRO_PREFIXES这种风格的很长的宏前缀,#include防卫语句,或是临时使用#undef来处理库中的宏。框架
另外一方面,工具也必须可以处理重复解析相同头文件时所面对的内在可伸缩性问题,由于即使程序员并不但愿,但不一样的处理环境仍是可能影响头文件的解释方式。函数
模块试图解决这一问题,它的理念是:隔离特定库的接口,并将其一次性编译为一种高效的、序列化的表示形式,当使用该库时,能够高效地导入,从而改进程序员的体验和编译过程的伸缩性。工具
该提议的基本前提是,做为一种加速编译并容许复用以前解析过的头文件的手段,即便编译最简单的文件,也要避免使用预处理器来包含大量头文件。在一个与“Hello World”同名的例子中,他强调到,一个包含64个字符的C程序通过预处理变成了11 074个字符,而一个包含81个字符的C++程序预处理后变成了1 161 033个字符。他还指出,由于包含要依赖于预处理器当时的状态,因此从新解析头文件可能让程序很脆弱(好比,若是在 #include 以前使用#define FILE "myfile.txt",预处理器会破坏头文件,从而致使构建失败)。spa
他的建议是使用一个新关键字import来加载模块。不一样于预处理器的文本包含方式,编译器可以理解该模块是一个固定的版本,因此只解析一次。若是屡次使用相同的模块,可使用前面解析过的同一数据结构,不须要每次都从新解析。调试
模块也能够嵌套,这容许导入子模块;在所给的例子中,他演示了std模块中的子模块stdio可使用import std.stdio来包含。导入模块以后,其中的全部公开API 就都导入到客户代码中了,但非公开API是隐藏的。为了实现这种控制,模块须要声明哪些接口是公开的,哪些是非公开的,这能够利用public 关键字:接口
// stdio.c export std.stdio: public: typedef struct { … } FILE; int printf(const char*, …) { … }
请注意,在这个例子中,仅提供实现文件就能够了,不须要头文件。export包含了模块的名字,这里就是std.stdio。public用于区分API 的公开部分与非公开部分。这能够编译为库以及带有充分元数据的函数类型和宏,供客户代码使用。进程
固然,这只是对将来的一个建议,并不是标准。那么这种方式应如何实现呢?建议使用头文件来处理现有模块的公开API,并将模块定义为一组头文件:开发
// /usr/include/module.map module std { module stdio { header "stdio.h" } module stdlib { header "stdlib.h" } module math { header "math.h" } exclude header "assert.h" } module ClangAST { umbrella "AST/AST.h" module * { } } // 可使用“import ClangAST.Decl”来导入AST/Decl.h
为便于之后生成模块(部分缘由是方便Objective-C框架导出模块),“umbrella module”机制容许将一个目录下的一组头文件做为单个模块导出。
适于处理模块的编译器能够在头文件上利用单独的一遍(Pass)来构建模块,以后在随后的头文件中复用该模块的信息。(编译好的模块应采用什么格式还没有指定,可能交由具体的编译器定义。)模块中也能够加入附加的元信息,好比说明模块运行所需的库。这容许编译器处理每一个模块所需的连接标记,从而避免了用户在连接时提供一大堆-l标记。
要使用模块,客户代码惟一须要修改的是将#include替换为等价的import。此外,由于在预处理后,模块中带有导出的函数和类型等信息,所以可以更好地进行编译诊断;利用这些信息,编译器报错和IDE快速修复等功能也能提示所需的import,而不只仅是直接失败。
最后,复用模块信息也容许将调试信息与模块关联起来,而非让这些信息重复出如今每一个目标文件中。编译器和连接器就能够少生成一些调试信息,反过来又加速了编译过程。模块也为调试器提供了额外的类型信息(而不是将类型信息内联到每一个目标文件中),所以调试器能够报告模块中定义的正确类型。
模块提议的净效应是,它提供了一种可以兼容现有工具的迁移途径,同时,在用户无需对原有代码进行多少修改的条件下,还带来了一些优势(主要是提高了编译速度,并改进了诊断错误消息和调试)。它也支持文件增量式升级,支持增量式地将单个预处理器指令切换为基于模块的导入机制。同时无需将编译速度测量当作模块表示的一部分,该工做已经在LLVM实现这些模块时进行了。虽然模块机制没有考虑版本或命名空间(很大程度上是由于必须知足向后兼容性),但该机制若是得以普遍应用的话,可以显著提高C和C++程序的编译速度。此外,向后兼容性被明确提了出来,像LLVM的块(block)规范,当须要的时候极可能用于支持其余编译器或规范中的包含。不过,在广为使用的C和C++编译器中,LLVM编译器工具链是惟一的一个保持创新并以身做则的了。其余编译器是否会引入这些特性,可能取决于LLVM 实现方案可否成功以及可以带来哪些益处。