点击上方“C语言与CPP编程”,选择“关注/置顶/星标公众号”
干货福利,第一时间送达!
最近有小伙伴说没有收到当天的文章推送,这是因为微信更改了推送机制,导致没有星标公众号的小伙伴刷不到当天推送的文章,无法接收到一些比较实用的知识和资讯。所以建议大家加个星标??,以后就能第一时间收到推送了。
bv1hapneree6401444522.png
转自 | CSDN(ID:CSDNnews) 作者 | Jan Kammerath 翻译 | 郑丽媛 【CSDN 编者按】在软件开发领域,关于编程语言的选择一直是开发者们热议的话题。尤其是对于像 Linux 内核这样对性能和稳定性要求极高的项目,语言的选择更是至关重要。Linux 内核创始人 Linus Torvalds,长期以来对 C++ 持批评态度,并明确拒绝将其用于 Linux 内核开发。在本文中,作者将深入探讨 Linus Torvalds 对 C++ 的批评,分析其背后的技术原因,并思考这些观点对现代软件开发的启示。
多年来,Linux 的创始人 Linus Torvalds 一直对 C++ 持批评态度,并且明确拒绝将其用于 Linux 内核开发。他不仅公开表达了自己的观点,还提出了许多值得深入探讨的反对使用 C++ 的理由。
虽然 C 和 C++ 几乎相同,但实际上它们并不一样。C++ 通常被认为是 C 的面向对象“版本”,或者说是 C 的继承者。但实际上,C++ 是对 C 语言的一种扩展,引入了面向对象、构造函数、析构函数、模板、异常处理、命名空间和运算符重载等特性。所有这些扩展都带来了各自独特的挑战和编程范式——不出所料,Linus 反对 C++ 的技术论点也都源于这些扩展。
q2h0ier5uum6401444622.png
du3kh2dfs516401444722.png
Linus 对 C++ 的主要批评
Linus 提出的反对 C++ 的主要论点,可以在内核邮件列表中的 "编译 C++ 内核模块 + Makefile "一栏中找到,相关回应最早可以追溯到 2004 年。接下来,让我们撇开这些邮件消息中的个人争论,专注于论点本身。
(1)C++ 中的异常处理
“整个 C++ 的异常处理机制从根本上就是错误的。对于内核来说,它更是错得离谱。”C 语言依赖返回值来指示错误,而 C++ 则倾向于使用异常。异常可以在代码的任何部分抛出,这使得抛出异常的代码在某种程度上变得不可预测。它们是非确定性的,并为 C++ 带来了完全不同的错误处理范式。错误处理是编程的基本需求,而 C++ 的不同方法本质上使其成为了一种完全不同的编程语言。
与 C 语言简单的返回值方法相比,异常的不可预测性和实现方式的多变性使得调试异常非常困难。如果将异常引入 Linux 内核,那么在将所有内容迁移到 C++ 的若干年里,内核将至少有两种不同的错误处理方式。
虽然这对于编写 GNOME 的绘画应用程序来说并不是什么问题,但对于拥有 3000 万行代码的 Linux 内核来说,这确实是一个真正的风险和威胁。在 Linux 内核中引入异常处理,将不可避免地导致内核变得更加不稳定。尽管内核变更的测试和审批过程非常严格,但要完全避免所有错误是不可能的。
(2)C++ 编译器中的内存管理
“任何喜欢在背后隐藏内存分配等操作的编译器或语言,都不是内核开发的好选择。”异常处理确实带来了一些由编译器隐藏起来的开销。这个关于“隐藏内存管理”的论点,指向 RAII(资源获取即初始化)或 C++ 中通过析构函数进行的自动内存管理。
Linux 内核的内存管理非常精细且经过高度优化,其中许多性能优化已经将编译器推向了极致。在内核开发团队已经面临编译器挑战的情况下,引入这种自动化的编译器特性是有风险的。
这不仅会导致使用 RAII 的模块性能下降,还会引入一个新的依赖关系。Linux 内核将更加依赖编译器。尽管这种依赖非常小且微不足道,但对于一个在全球数十亿设备上运行的 3000 万行代码库来说,这就是一个很大的问题。
(3)C++ 与 C 中的面向对象编程
“你可以在 C 语言中编写面向对象的代码(对系统文件等场景很有用),而无需使用 C++中的那些垃圾。”面向对象编程(OOP)是支持使用 C++ 而非纯 C 的主要论点,然而在 Linus 看来,相较于 C++ 带来的复杂性和风险这并不值得。他认为在 C 语言中也可以实现基本的面向对象编程:通过使用模仿 C++ 类的结构体(structs),可以在纯 C 中实现面向对象的概念。以下是一个非常简单的例子,展示了如何在纯 C 中实现面向对象编程:
#include #include
// Define the "class" using a structtypedef struct { int value; // Function pointer for a method void (*increment)(struct Person *self);} Person;
// Method implementation for incrementvoid increment_person(Person *self) { self->value++;}
// Constructor-like function to initialize a PersonPerson* person_new(int initial_value) { Person *p = (Person*)malloc(sizeof(Person)); p->value = initial_value; p->increment = increment_person; return p;}
// Destructor-like functionvoid person_free(Person *p) { free(p);}
int main() { // Creating an instance of Person Person *p = person_new(5); // Using the method printf("Initial value: %d
", p->value); p->increment(p); // Incrementing the value printf("After increment: %d
", p->value);
// Freeing the allocated memory person_free(p); return 0;}
面向对象编程并不是 Linux 内核运行的技术要求,它只是一个不同的编程范式或概念。将 OOP 引入 Linux 内核的目的是为了提高模块化、封装性和代码复用性,从而简化维护和开发。这些优点都属于开发者体验的范畴。
Linux 内核之所以在全球数十亿设备上广泛采用,是因为其性能和稳定性。而这些优势是以牺牲开发者体验和开发周期为代价的:任何旨在改善开发者体验的改进都会不可避免地削弱内核的性能和稳定性。性能、稳定性和易用性之间总是需要彼此权衡。
因此,Linux 内核开发在很大程度上忽视了开发者体验,以实现尽可能更高的稳定性和性能——这是内核的设计哲学。因此,引入 OOP 不仅是一种风险和威胁,更是对内核哲学的彻底改变。Linux 用户,尤其是在关键任务环境中的用户,既不会欢迎也不会容忍这种改变。这也再次证明了 Linus 的观点是有道理的。
(4)C++ 库和依赖项的稳定性
“当这些库无法工作时,会带来无尽的痛苦(任何告诉我 STL 和 Boost 是稳定和可移植的人,简直是满嘴跑火车,而且一点都不好笑)。”在用户级或用户空间应用程序开发的背景下,Boost 和 STL 可以被认为是“稳定的”。然而,当涉及到内核开发以及 Linux 内核的要求时,“稳定”这个词有着更为严格的意义。
提出使用 C++ 的标准模板库(STL)或 Boost 库,其目的是为了改善开发者体验。然而,引入这些依赖项所带来的风险与 RAII 相同,都会以牺牲性能和稳定性为代价。
除了性能和稳定性之外,还有一个所有权的问题。内核引入的每一个依赖项,都会将责任转移给该依赖的维护者。随着责任的转移,所有权也会转移,这就带来了安全风险。例如 CVE-2024–3094 漏洞,这是由一个名为“Jia Tan”的神秘用户在 liblzma 库中引入的一个后门。
(5)低效且臃肿的抽象
“低效的抽象编程模型,两年后你会发现某些抽象并不高效,但此时你的所有代码都依赖于这些漂亮的对象模型,而你无法修复它,除非重写你的应用程序。”这个观点与 C++ 本身关系不大,而是针对面向对象编程和继承的概念。面向对象编程中的设计模式旨在提供解决常见问题的蓝图方案,这是有用的,所有进入 OOP 领域的人都应该注意。内核开发人员是非常有能力的软件工程师和程序员,他们知道如何在何时何地应用 OOP——然而,他们也清楚 OOP 并非没有缺点。除了技术上的考虑,例如内存开销和更大的二进制文件外,还有逻辑上的陷阱,即使是经验丰富的程序员也无法免疫。
Linus 的观点是,错误应用的类层次结构和设计最终会导致不可维护的代码。在 OOP 中,创建臃肿的类层次结构并不少见。尽管内核开发人员肯定不会在大规模上犯这样的错误,但在小规模上可能会。而在一个像 Linux 内核这样庞大且复杂的代码库中,这种情况同样危险。
无论你的代码在技术上有多好,如果你在逻辑上搞乱了类层次结构、结构和设计,你就可能陷入维护的死胡同。这种情况下,可能就需要对类结构进行彻底重构。事实上,OOP 应用程序在短短几年后就需要对应用架构进行完全重构的情况并不少见。如果使用了 C++,Linux 内核也有可能会走上这条道路——这种风险真的值得吗?
(6)你最终还是会用回纯 C
“换句话说,唯一能写出高效、系统级且可移植的 C++ 代码的方法,就是限制自己只使用那些基本上在 C 中可用的东西。”上述提到的特性都不是 C++ 所必需或强制要求的,你完全可以像写 C 语言代码一样去编写 C++ 代码。Linus 的观点是:既然如此,那为什么还要用 C++ 呢?如果你用 C 语言的风格写 C++ 代码,,那么 C++ 编译器就变得毫无意义了。
“将项目限制在 C 语言中,意味着人们不会搞砸,也意味着你会吸引到许多真正理解底层问题的程序员,他们不会因为愚蠢的‘对象模型’而搞砸事情。”这是他在 2007 年 9 月 6 日的邮件列表帖子最后一段中提到的第二个观点。这句话的核心意思是,C 语言会吸引那些专注于硬件并解决实际问题的内核开发者,他们不会迷失在面向对象编程的外表和混乱中。
毕竟,开发者也是人,而这个观点强调了人在编程中的重要性。它表明,作为 Linux 世界的“独裁者”,Linus 不仅要考虑技术问题,还要像其他领导者一样管理人。他是目前全球最大开发者社区之一的教父,应该继续保持这种状态。某种程度上来说,他必须应对和管理全球超过 2000 万开发者的期望、希望和梦想。
(7)下一步是什么,Rust、Go 还是甚至原生 Java?
“C 语言最终是一种非常简单的语言,这也是我喜欢 C 语言、以及许多 C 程序员喜欢 C 语言的原因之一。然而,也正是因为其简单性,它也容易出错,而 Rust 就不会。” ——源自 Linus Torvalds 与 Dirk Hohndel 的一次对话这里顺便提一下,已经有关于在 Linux 内核中使用 Rust 的讨论了。下一步是什么,Go、C#,还是通过 GraalVM 编译成原生二进制文件的 Java?部分开发者会提出这些问题,就像有些人提出 C++ 一样,这并非出于恶意。
Linus 必须在某个地方划清界限。Linux 内核 99% 是纯 C 语言,剩下的 1% 是纯汇编语言。内核的架构是人类历史上最干净的操作系统内核架构。而内核面临的挑战不仅仅是编程挑战,还包括中断处理、上下文切换和其他低级 CPU 操作等技术挑战。
内核需要提供设备驱动程序、文件系统、Linux 出色的网络堆栈、内存管理、进程调度以及更多功能。这些功能都具有很高的技术复杂性,添加任何与这些挑战无关、且不能直接为用户带来好处的复杂性,都是非常值得怀疑的。在 Linux 内核中引入 Rust 代码,比引入 C++ 会带来更多的复杂性,因此许多人对 Rust 引入内核这件事的看法是“不”。
(8)拒绝 C++ 的理由是否合理?
视情况而定,可以说是“是”也可以说是“否”。Linus 提出的论点在用户级应用程序(如绘图应用程序)的背景下可能是可笑的,但在内核开发的背景下,这些观点就是非常重要且合理的,因为它们突出了内核开发的严肃性。
Linux 的成功源于性能和稳定性,这得益于其 99% 纯 C 代码库和 1% 汇编代码的高度优化。引入 C++、异常处理、面向对象编程或任何其他未经测试的新奇技术,必然会对其产生负面影响。虽说把 C++ 形容为新奇技术或未经过测试的技术听起来有些荒谬,但对 Linux 内核来说确实如此。
somx3yun3ft6401444822.png
我们可以从 Linus 对 C++ 的拒绝中学到什么?
Linux 内核是全球独一无二的软件工程奇迹。超过 3000 万行代码运行在全球数十亿台机器上,其性能和稳定性无与伦比,这是内核开发者专注和奉献的直接结果。关于 systemd 与 SysVinit 的持续争论表明,开发者对 Linux 的重大变更既十分重视,又考虑到各种技术细节。
Node.js 应用中常见的“依赖地狱”与精心编写的 3000 万行 Linux 内核代码形成了鲜明对比。我们从 Linux 内核关于 C++ 的争论中可以学到的关键教训是:我们应该对依赖关系做出非常明智和理性的决策,包括考虑长期影响。
此外,Linus 对待 C++ 甚至 Rust 内核开发的态度表明,“使用合适的工具来完成工作”是多么重要。将一切都标准化为纯 C 已经使 Linux 内核标准化,但它仍然充满着内核开发者每天努力解决的挑战。当你的同事明天想用 Python 实现你 Go 应用的一个功能时,你可以考虑一下 Linus 的方法。
vfqojwjni5k6401444922.png
结语
下次当你被推进医院的 MRI 扫描仪时,问问自己是否希望那东西能够抛出异常。当“智能冰箱”变得有自我意识并吃掉你的零食时,你可能会觉得有趣,但如果 MRI 扫描仪也自行其是,那可能会致命。
如今 Linux 内核无处不在,所有智能设备,包括计算机、手机、服务器、网络设备、嵌入式系统、游戏机、医疗设备、家用电器、可穿戴设备、汽车、交通信号灯、工业机械、航天系统、铁路控制系统、ATM、空中交通管制系统、卫星、科学仪器、医疗设备、农业机械、太空望远镜、潜艇、战斗机和巡航导弹……这些只是其中的一部分,你愿意冒风险去破坏它们吗?
虽然 Linus 显然厌倦了关于内核开发中 C++ 的问题,但提出并审视这个问题是很重要的。尽管答案是“不”,但我们可以从这个答案中学到一些非常重要的东西:这是花哨的还是必要的?我这样做是为了自己还是为了我的用户?这样做会产生什么样的长期影响?
原文链接:https://medium.com/@jankammerath/linus-torvalds-critique-of-c-a-comprehensive-review-ea8374084abf
p2b1ancb52l6401445023.gif
推荐阅读 点击标题可跳转1、C++训练营,来了!
2、HarmonyOS 学习资料分享(无套路免费分享)
我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。
es1s3vstrmw6401445123.png
欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会。
hrzidfpalag6401445223.png
加个微信,打开另一扇窗
感谢你的分享,点赞,在看三连
wtum34xffmx6401445323.gif
|