C++ 的两个派系之争
关于 C++ 的未来方向,存在众多争议和激烈辩论。无论是在 Reddit 的讨论还是 C++ 标准委员会的正式会议中,都难以避免地涉及到立场和派系的争执。这已成为一个不容忽视的事实。
C++的现状
当前,C++ 阵营面临以下状况:C++ 的演进工作组(EWG)刚刚达成了关于采纳 P3466 R0 的共识——这表明未来 C++ 的发展方向需要重新确认: ● 这表示要避免 ABI 的破坏,保持与 C 语言及旧版 C++ 的兼容性。 ● 这也意味着要避免“病毒式注释”(例如,不进行全周期注释)。 ● 这进一步强调了对一系列不兼容目标的处理,即拒绝 ABI 破坏和零成本原则。 ● 无论好坏,这都是对 C++ 语言发展路径的加强。
然而,C++ 的发展也遭遇了挑战,包括美国政府机构(如网络安全与基础设施安全局(CISA)、国家安全局(NSA)和白宫)建议停止使用 C++,警告技术行业避免使用内存不安全的语言。科技巨头们也在逐渐放弃 C++ 而转向 Rust。
ISO C++ 委员会主席 Herb Sutter,在微软工作 22 年后,离开了微软,加入了 Citadel Securities 担任技术研究员。据悉,微软正在用 Rust 重写其核心库。早在 2022 年,Microsoft Azure 首席技术官 Mark Russinovich 就建议业界停止使用 C/C++。“在语言选择上,现在应该停止启动任何新的 C/C++ 项目,并在需要非语言特性的场景中使用 Rust,”他说。“为了安全和可靠性,业界应该宣布这些语言已过时。”
谷歌在 2021 年表示,正在全力支持 Rust。谷歌称:“Android 平台代码的准确性是每个 Android 版本安全性、稳定性和质量的首要任务。C 和 C++ 中的内存安全错误仍然是最难解决的问题。尽管我们投入了大量资源来检测、修复和缓解这些问题,但内存安全错误仍然是稳定性问题的主要因素,占 Android 高严重性安全漏洞的约 70%。Rust 通过编译时检查强制执行对象生命周期/所有权,并使用运行时检查确保内存访问的有效性,从而提供内存安全保障。在保持与 C 和 C++ 相当的性能的同时,实现了安全性。” 谷歌实际上已经开始开发 C++/Rust 互操作工具。 工具链接:https://github.com/google/crubit
AWS 也在大规模使用 Rust。AWS 表示,“Rust 提供了完善的工具、强大的包管理器(Cargo),最重要的是——一个快速增长且充满热情的开发者社区。随着 Rust 的流行,越来越多的知名组织,包括 AWS,将其用于性能和安全性至关重要的关键应用。例如,Amazon S3 使用 Rust 尝试以个位数毫秒的延迟返回响应。
AWS 的 Rust 产品组件还包括 Amazon CloudFront、Amazon EC2 和 AWS Lambda 等。”此外,Prague ABI 投票已经开始(即「C++23 不会破坏 ABI,但未来是否变化尚不清楚」)。据悉,谷歌已大幅减少其在 C++ 开发流程中的参与,转而开发自己的 C++ 后继语言。谷歌甚至发布了总结,概述了在尝试改进 C++ 时遇到的所有问题。这也给 C++ 的未来蒙上了阴影。
多年来,人们全力参与 C++ 标准委员会流程,但最终被彻底驳回的故事已广为人知,并在社区中流传。(即使是在 C 中已实现的功能也不例外。)模块化设计仍未实现,C++ 何时能拥抱模块化?基于这些情况,人们对 C++ 的未来感到担忧。事实上,许多人对 C++ 委员会控制混乱现状的能力失去了信心。
困在两种文化冲突之中
人们似乎正在寻找其他解决方案。
例如,谷歌自 ABI 投票以来,明显对 C++ 委员会的“流程”失去了信心。这并不是对语言本身的不信任,毕竟谷歌拥有世界上最大的 C++ 代码库之一,并一直为其提供良好的维护服务。所谓的失去信心,主要是对语言在面对多方面压力(包括潜在的政府法规、竞争语言的冲击、关键参与者对更高性能和更好安全保障的规划等)时能否持续发展的能力持怀疑态度。
那么问题的根源是什么?C++ 为何变得如此……固步自封?
这个问题不难回答,只需看看 Herb Sutter 关于配置文件的文章就能略知一二:“我们必须尽量减少对现有代码的变更需求。对于现有代码中已经存在的应用,数十年的经验一直表明,大多数拥有大型代码库的客户不能、也不会为了满足严苛规则而更改哪怕1%的代码行。除非监管要求强迫他们这样做,否则即使是出于安全原因也无法推动这方面举措。”——Herb Sutter
这话说得……但奇怪的是,人们似乎对此习以为常。
现在让我们与 WG21 成员页面上 Chandler Carruth 的记录进行对比:“我负责基于 Clang 构建的 C++ 工具和自动重构系统的设计工作,其现在属于 Clang 项目的一部分……在谷歌内部,我领导了将基于 Clang 的自动重构工具扩展到我们整个代码库(共涉及超过 1 亿行 C++ 代码)的努力。我们可以在 20 分钟内分析并对整个代码库执行重构。”看到了吗,他们似乎愿意进行变更。
遗憾的是,自动化迁移工具也是 C++ 阵营目前唯一能展示的应用案例。
基本上,我们看到了两大截然不同的 C++ 用户派系之间的冲突:
● 灵活、现代且能力强的科技企业,清楚地认识到自己的代码是一种资产。(请注意,这里不仅指大型科技企业,任何理智的 C++ 初创公司也会站在这一边。)● 除此之外的所有老牌企业,仍在为代码缩进等细节争论不休。部分年轻工程师甚至需要恳求管理层允许他们设置 linter。
相信未来一定会出现一支能够优雅处理迁移,并且立足版本化源代码构建其 C++ 技术栈的团队,但绝不会是目前仍强行使用 1998 年古老预建库的团队。 当然,这在实践中会是一个渐进的过程。我只能想象,要想将大型技术代码库从可怕的混沌状态转化成具备一定可管理性、可构建性、经过 lint 分析、拥有正确版本控制的改良形态,必然要付出无数汗水、泪水、成本甚至是牺牲。事后看来,我们当然可以轻描淡写地认为这一切都是历史发展的必然:谷歌等巨头的需求(即使用高度现代化的 C++ 代码、建立自动化工具与测试以及现代化基础设施)明显与其(非常强烈的)向下兼容意愿之间存在脱节。
我们甚至可以大胆地说,统一无方言的 C++ 概念似乎在多年前就已经消亡。目前我们至少面对着两条主要的 C++ 发展路线:
● 任何稍微现代的 C++,可能至少是从 C++17 开始。它们支持 uniqe_ptr, constexpr, lambdas 和 optional。一切都可以从版本化的源代码构建,使用某种专用、干净且统一的构建流程,该流程至少要比原始 CMake 稍微复杂一些,而且只要认真观察应该就能顺利起效。其还具备某种静态分析器、格式化程序、linter 等。总之,要支持一切有助于保持代码库干净和现代的协议。
● 不符合以上特征的其他产物。比如那些长期运行在中等规模银行里那古老、布满灰尘的服务器当中的 C++ 项目。这些 C++ 往往依赖于某些极其陈旧的编译代码块,而且对应的源代码已经丢失,且无法联系到其原始作者。还包括一切部署在微型服务器上的 C++,要在其他环境下正常启动,工程师们往往需要一个月时间才能厘清其中的所有隐式依赖项、配置和环境变量。这些就是被广泛归类为成本中心的遗留代码库。
大家会注意到,两派最大的分歧并不在于 C++ 本身,而在于依托工具或其他手段以干净且定义明确的方式,立足版本化源代码进行构建的能力。理想情况下,我们甚至并不需要记住前一个人设置的标记或者环境变更,即可顺畅部署而不致引发崩溃。很多人会强调,这类生态工具并不在 C++ 标准委员会的职责范围之内。这话也没错,但工具之所以不在他们的职责范围内,是因为 C++ 标准委员会放弃了这份责任(他们只专注于 C++ 语言的规范,而非具体实现)。当然,这跟 C++ 语言本身的设计有关,属于遗留问题,我们也不能过多责怪。总之如今的 C++ 已经成为一套用于统一不同实现的囊括性标准。
但相比之下,如果要说 Go 语言有哪件事做得最为正确,那就是它证明了工具的重要性。相比之下,C++ 就像是来自史前时代、来自 linter 被发明出来之前。C++ 没有统一的构建系统,甚至没有勉强能算统一的包管理系统,因此解析和分析起来都极其困难(这对配套工具来说很糟糕),每一次更改都会带来一场艰苦卓绝的折磨和对抗。这两个派系之间还存在着巨大且仍在不断恶化的裂痕。老实说,我认为这种裂痕不可能很快消失。C++ 委员会似乎致力于(当然,这已经是很高情商的说法了)保持向下兼容性,甚至愿意为此不计成本。
结果是怎样的呢?
因此,配置文件机制的现状并不旨在协助那些已经采用现代技术和精通开发技巧的 C++ 企业,而是旨在在不改变现有代码的前提下实现改进。
模块化机制也是同样的情况,它允许开发者仅将头文件作为模块引入,而不会引起任何向下兼容性问题。自然,每个人都希望那些能够无缝集成且不需要修改旧代码的功能。但这些功能的设计初衷(也是其核心特点)显然是为了适配“遗留 C++ 代码”。任何需要对遗留 C++ 代码进行迁移的功能改进在 C++ 委员会那里都行不通,正如 Herb Sutter 所说,我们不能期待人们愿意承担这样的迁移成本。
这是我在查阅 C++ 相关资料时最深刻的感受:C++ 阵营分裂成两个派别,一个是现代 C++,另一个是遗留 C++。这两大派别之间存在严重的分歧,许多文章只针对其中一个特定群体的需求。C++ 委员会正努力防止这种分歧的进一步扩大。也许正因为如此,Sean Baxter 在推动 Safe C++ 方面的努力注定是徒劳的。这可能会引发一场颠覆性的变革,可能会创造出一种全新的 C++ 编程方式,但遗憾的是,这种变革似乎无法实现。
此外,还有一个问题,可能 C++ 标准委员会中的某些成员特别固执,不愿意听取任何与自己意见相左的观点。我并不是在指责任何人,但我多次听说 C++ 委员会存在双重标准,比如“如果您希望提案获得通过,您需要在多个工作编译器上进行全面而有效的实现,但我们仍然乐于支持一些未经充分验证的大型项目(如模块和配置文件机制)。”如果这种状况持续下去,我严重怀疑 C++ 阵营可能会彻底分裂。而这一切还没有考虑到破坏 ABI 兼容性所带来的诸多问题。
[*]如果有人对此持怀疑态度,也可以将此视为对 Rust 的“全生命周期注释”和 Sean Baxter 的“Safe C++”提案的明确拒绝。即使从最乐观的角度来看,这也至少表明委员会对现有代码重构需求的漠视。
[*]“你不会为你不使用的东西买单。”实际上,现有的 C++ 功能只有在我们实际使用时才会影响运行时性能。这显然与稳定的 ABI 相冲突,因为稳定的 ABI(可以视为 C++ 的一个特性)会排除某些性能改进措施。
[*]我认为 Carbon 比大多数人想象的要有趣得多。未来我可能会专门写一篇文章来讨论。
[*]C++ 阵营真的在瓦解吗?这取决于你从哪个角度看。如果从 C++ 代码的存续来看,那么不会,至少原有的 C++ 成果还会继续存在。
[*]注意,我指的是 C++ 阵营本身,与之相关的各种编译器和编译扩展是完全不同的话题。
网友怎么看?
该帖子在 Reddit 社区中引发了诸多讨论。ID 名为 ravixp 的 Reddit 用户对上述观点表示认同。“这段话引发了我的强烈共鸣,原因是我曾亲眼见证了一个庞大的 C++ 代码库,在历经数十年的开发过程中,如何从“传统”逐步过渡到“现代”C++。这一转型并非一蹴而就,而是由不同团队在不同时间和以不同速度独立决定的,至今仍在持续进行中。任何新的代码现代化计划都不得不面对这样一个现实:代码库中的各个部分起始的现代化水平参差不齐。(试想,在一个同时充斥着 std::string、C 风格字符串以及源自 20 年前、因当时 STL 尚不完善而自创的字符串类型的代码库中,引入静态分析将是一项多么艰巨的任务!)然而,现代化的代价高昂。这里所指的现代 C++,并不仅仅是编写方式上的差异,它还意味着可能需要重建整个工具链上层结构,以使代码符合现代标准,并拥有一支能够紧跟 C++ 发展步伐的工程团队。重要的是要认识到,这里的冲突并非源于对传统 C++ 与现代 C++ 的个人偏好之争,而是关乎能否承担得起现代 C++ 转型的成本。C++ 确实需要变革,但真正的挑战在于我们共同能够承受多大的变革,以及如何在有限的投入中获得最大的价值回报。
ID 名为 KittensInc 的 Reddit 用户解释了美国禁止 C++ 的合理性,因为美国政府认为 C++ 代码库正成为负担,他们倡导避免重蹈覆辙以减少错误。缓冲区溢出等问题的预防变得重要,导致企业要求第三方审计以确保代码质量。企业面临现代化改造的抉择,否则可能面临倒闭风险,而采用现代开发实践的企业能更轻松应对。
“我并不惊讶于未来几年这种动态可能会发生变化。遗留的 C++ 代码库正迅速成为一项沉重的负担。美国政府已经认识到,通过采取不同的设计决策可以有效避免一类错误的发生,并正在积极倡导避免重蹈覆辙。我认为,相关责任人只是时间问题,他们终将跟上这一变革的步伐。如果我们认为缓冲区溢出等问题是完全可以预防的,那么当这类问题成为安全事件的根源时,黑客攻击、勒索软件、数据泄露的保险拒赔也将变得合乎逻辑。在这种背景下,企业会愈发要求软件供应商提供第三方代码库的 linting 审计,以确保代码质量。我们已经到了一个十字路口,不进行现代化改造的代价将变得无法承受。对于代码库而言,要么进行现代化改造以适应新时代的需求,要么面临公司倒闭的风险。那些采用现代开发实践的企业,只需借助一些简单的分析工具并完成必要的文档填写,就能轻松应对;而那些缺乏有效工具、且技术债务在代码库中不断累积的企业,将面临严重的困境。”
但也有人认为,C++ 代码库的安全性不取决于其现代或遗留属性。90 年代 C++ 库注重安全性并多用运行时检查,而现代 C++ 则减少运行时检查,将更多内容纳入类型系统,未定义行为用于优化。
“安全性的实现与 C++ 代码库是现代还是“遗留”并无直接关联。事实上,在 90 年代,流行的 C++ 库在开发时普遍注重安全性,并广泛采用了运行时检查来确保代码的正确执行。在当时,未定义行为并非被视为编译器可以对代码做出严格假设并执行激进优化的手段,而是被视为一种在不同平台和实现之间实现灵活性的合理方式。然而,进入 21 世纪初期,“现代”C++ 的发展方向发生了转变,决定减少运行时检查,并尝试将所有内容纳入类型系统中。对于那些无法通过静态验证的内容,它们被归类为未定义行为,编译器则可以根据优化需求进行自由处理。”
页:
[1]