在 2025 收官前,看清 Data + AI 的真实走向,点击查看 BUILD 大会精华版 了解详情
写点什么

LLVM 提议向 C 语言中加入模块机制

  • 2012-12-05
  • 本文字数:2096 字

    阅读完需:约 7 分钟

在今年 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”,预处理器会破坏头文件,从而导致构建失败)。

他的建议是使用一个新关键字 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 实现方案能否成功以及能够带来哪些益处。

查看英文原文 LLVM Proposes Adding Modules to C

2012-12-05 05:473258
用户头像
臧秀涛 略懂技术的运营同学。

发布了 300 篇内容, 共 151.9 次阅读, 收获喜欢 35 次。

关注

评论

发布
暂无评论
发现更多内容

极客时间运维进阶训练营第八周作业

好吃不贵

多名网络主播因恶意炒作被列入警示名单,如何打击这种违规行为

石头IT视角

上海靠谱的前端培训机构有没有推荐

小谷哥

华为云12·12直播EI专场即将开始,满足电商行业全场景搜索需求

科技怪授

华为云

横空出世!阿里巴巴Spring全家桶实战笔记真香

Java永远的神

spring 源码 架构师 springboot SpringCloud

相见恨晚!Git这些功能太好用了!

Jackpop

终于被我发现了这个推特视频下载的方法!超级简单!支持苹果安卓双系统!

frank

推特视频下载

java自学好还是培训好?

小谷哥

下一个AI舞台,名叫煤矿

脑极体

大数据培训出来就业前景如何

小谷哥

架构实战模块1作业

Geek_e3a35c

场景 | 大型电商企业运营管理数字化解决方案

九科Ninetech

云渲染是什么?云渲染和自己渲染有什么区别?

Renderbus瑞云渲染农场

云渲染 渲染农场 云渲染平台

模块二 -- 朋友圈高性能复杂度分析

陈实

「架构实战营」

来聊一聊 ElasticSearch 最新版的 Java 客户端

江南一点雨

Java elasticsearch springboot ES

声网管浩森:元宇宙派对场景的最佳实践

RTE开发者社区

实践 元宇宙 RTE2022

SpringBoot整合Swagger2,再也不用维护接口文档了!

@下一站

12月日更 12月月更 springboot整合 swagger2

大数据培训工作就业前景怎么样

小谷哥

凭借这份Java面试复盘笔记,我在2022寒冬之际也斩获了多张Offer!

程序员小毕

spring 程序员 后端 架构师 java面试

选取数据的原则

穿过生命散发芬芳

数据分析 12月月更

C++开发,这些GUI库一定不要错过!

Jackpop

一文带你快速上手云日志服务

云计算 运维 日志管理

什么样的web前端培训靠谱?

小谷哥

Verilog的语句块

芯动大师

Verilog Verilog语法 Verilog语句块

Python开发游戏?也太好用了吧!

Jackpop

直播继续!华为云Solution as Code一键高效上云,解决方案开箱即用

科技怪授

华为云

关于K8s集群环境工作组隔离配置多集群切换的一些笔记

山河已无恙

k8s管理 K8s 多集群管理 12月月更

浪潮 KaiwuDB 陈磊:布局数字能源,创新助力 “双碳”

KaiwuDB

Chrome浏览器竟然可以用ChatGPT了!

Jackpop

LLVM提议向C语言中加入模块机制_C++_Alex Blewitt_InfoQ精选文章