70年代初,贝尔实验室创建了c语言,它是开发unix的副产品。很快c就成为了最受欢迎的编程语言之一。但是对于bjarne stroustrup来说,c的表达能力还不够。于是,他在1983年的博士论文中扩展了c语言。
于是,支持类的c语言诞生了。
当时,bjarne stroustrup明白编程语言有许多组成部分,除了语言本身,还有编译器、链接器和各种库。提供熟悉的工具有助于语言被广泛接受。在这种历史背景下,在c语言的基础上开发c++也是有道理的。
40年后,c和c++都在行业中得到了广泛使用。但是,互联网上的c开发人员认为c++是有史以来最糟糕的人类发明,而许多c++开发人员则希望有朝一日c语言灰飞烟灭。
01
究竟发生了什么事?
从表面上看,c和c++都可以满足相同的用例:高性能、确定性、原生但可移植的代码,可用于最广泛的硬件和应用程序。
但是,更让c自豪的是它是一门低级语言,更接近汇编。
而c++,从诞生第一天开始就充斥了各种奇怪的东西。例如析构函数这个黑魔法。自作主张的编译器。尽管很早c++就有了类型推断功能,但是80年代中期的开发人员还无法接受这个概念,因此bjarne stroustrup不得不删除了auto,直到c++ 11又重新添加回来。
从那以后,c++就不断加入各种工具来实现抽象。很难说c++是一种低级语言还是高级语言。从设计目的上来说,c++两者都是。但是在不牺牲性能的情况下,建立高级抽象是很困难的。于是c++引入了各种工具来实现constexpr、move语义、模板和不断增长的标准库。
从根本上讲,我认为c信任开发人员,而c++信任编译器。这是一个巨大的差异,单凭“两者的原生类型相同”、“while循环的语法相同”等简单一致是无法掩盖的。
c++开发人员将有这些问题归咎于c,而c开发人员则认为c++过于疯狂。我觉得站在c的角度看c++,这种说法也很正确。作为c的超集,c++确实很疯狂。一个经验丰富的c开发人员面对c++可能没有熟悉的感觉。c++不是c,这就足以引发互联网上的激烈争论。
然而,虽然我不喜欢c,但也没有权利取笑c。尽管我有一定的c++经验,但用c编写过的代码少之又少,而且肯定是很糟糕的代码。好的编程语言包括良好的实践、模式、惯用写法,这些都需要多年的学习。如果你尝试用编写c++的方式写c的代码,或者用c的方式编写c++的代码,那感觉一定很糟糕。即便你懂c,也不一定会c++,反之亦然,懂c++也不一定会用c编程。
那么,我们是否应该停止说c/c++,为这两个不幸的命名而感到悲哀吗?也不至于。
尽管c++的设计理念与c不一样,但是c++仍然是c的超集。也就是说,你可以在c++转换单元中包含c的头文件,这样依然可以通过编译。而这正是造成混乱的地方。
c++不是c的扩展,它是由不同的委员会、不同的人独立设计的标准。从逻辑上讲,喜欢c++理念的人会参与c++社区以及c++标准化的过程,而其他人可能会尝试参与c。无论是c的委员会还是c++委员会,他们表达意图和方向的方式只能通过各自的最终产品:标准;而标准是众多投票的成果。
然而,编译器很难知道它正在处理的是c头文件还是c++头文件。
extern “c” 标记并没有得到广泛一致的使用,而且它只能影响修饰,而不会影响语法或语义。头文件仅对预处理器有影响,对于c++编译器而言,所有内容都是c++转换单元,因此也就是c++。然而,人们依然会在c++中包含c头文件,并期望它“正常工作”,而大多数时候也确实可以正常工作。
那么,我们不禁想问:
02
由不同地方的、不同的人开发的c++代码如何保持c的兼容性?
恐怕很难。
最近,一位同事让我想起了康威定律:
设计系统的架构受制于产生这些设计的组织的沟通结构。
根据这个逻辑,如果两个委员不互相合作,则他们创造的语言也不会互通。
c++维护了一个与c及其标准库的不兼容列表。然而该列表似乎并未反映出许多c11和c18中添加、但在c++中不合法的功能。更清晰的介绍请参见这个维基本科页面(https://en.wikipedia.org/wiki/compatibility_of_c_and_c%2b
%2b)。然而,仅仅列出两种语言之间的不兼容性,并不足以衡量二者的不兼容性。
那些存在于c++标准库中但主要声明来自c的函数,很难声明成constexpr,更难声明成noexcept。c的兼容性会导致性能成本,而c函数是优化的障碍。
许多c的结构在c++中都是有效的,但无法通过代码审查(如null、longjmp、malloc、构造/析构函数、free、c风格的类型强制转换等)。
在c看来,这些惯用写法可能问题不大,但在c++中可不行。c++具有更强大的类型系统,不幸的是,c的惯用写法在这个类型系统中凿了一个洞,因此实现c的兼容性需要在安全性方面付出代价。
别误会,c++仍然关心c的兼容性,某种程度上。然而,有趣的是c也很关心c++,某种程度上。实话实说,c对c++的关心程度可能高于c++对c的关心。看来,每个委员会还是在乎另一个委员会的工作。但我们很不情愿。
c++知道,许多基础库都是用c编写的,不仅包括libc,而且还有zip、png、curl、openssl(!)以及许多其他库,无数的c++项目都在使用这些库。c++不能破坏这些兼容性。
但是最近,尤其是在过去的十年中,c++的规模已远远超过c。c++拥有更多的用户,并且社区更加活跃。也许这就是为什么如今c++委员会的规模是c委员会的10倍以上。
c++是不可忽视的力量,因此c委员会必须考虑不破坏c++兼容性。如果非要说一个标准追随另一个标准对话,那么如今c++是领头者,而c是追随者。
现在,c++处于稳定的三年周期中,无论是风雨还是烈日,抑或是致命的新疫情。而c每十年左右才发布一次主版本。不过这也很合理,因为作为一种较低级的语言,c不需要发展得那么快。
c语言的环境也与c++完全不同。c多用于平台,更多地用于编译器。每个人(甚至他们的狗狗)都会编写c编译器,因为该语言的特性集很小,所以任何人都可以编写c编译器。而c++委员会真正考虑的实现只有四种,而且在每次会议上这四种实现都会出现。所以,c语言中的许多功能都是与实现有关的,或者是可选支持的,这样各种编译器不需要做太多努力就可以声称自己遵从了标准,据说这样委员会的人会比较高兴。
如今,c++更加侧重于可移植性,而不是实现的自由。这又是一个理念的不同。
03
因此,你的提议破坏了c的兼容性
我提议的p2178的一部分理论上会影响与c的兼容性。这样的话所有方案都不会令人满意。
有人可能会说,你可以先向c委员会提议你的新特性。这意味着需要召开更多会议。c会议的严格出席规则可能导致你无法参加会议,这就将那些不愿意花上数千美元成为iso会员的个人拒之门外。这是因为c委员会必须遵守iso的规则。
而且,如果新的标准刚刚发布,那么可能还需要等待十年时间,你的提案才会被考虑。最重要的是,如果c委员不理解或不在乎你正在努力解决的问题,那么你的提案就石沉大海了。或者他们可能没有精力来处理这个问题。而且,可能你也没有精力来处理c。毕竟,你的本意是要改进c++。实际上,哪怕会议上无人反对你的提议(尽管不太可能发生),如果有人让你先去跟c委员会的人讨论,就等于给你的提议判了死刑。
另一种可能的情况是,c委员会接受与c++中存在的版本略有不同的版本。true只能做一个宏来实现。char16_t需要通过typedef。char32_t不一定是utf-32。static_assert对应的是 _static_assert。
这类的情况还有很多,我们应该责备c吗?可能不应该。他们的委员会只是在尽力将c语言做好。反之亦然。在c++20中,指定的初始化器就受到了c的启发,但采取了略微不同的规则,因为如果完全一样的话就不符合c++的初始化规则。
对于这个问题,我也有责任。c有vla。如果当时我在,我一定会反对在标准c++中采用它,因为它导致了太多安全性问题。我也会坚决反对将_generic添加到c++中的提议。也许_generic的目的是减少由于缺乏模板或缺乏重载而导致的问题,但是c++有这两个功能,从我的角度来看,_generic并不适合我想象中的c++。
这两个委员会似乎对于对方语言的关心程度也不一样。有时我们会遇到兼容性非常好的情况(std::complex),有时完全不在乎兼容性(静态数组参数)。
这没有办法。别忘了每个委员会都是一群人,他们在不同的时间、不同的地点投票,而试图控制结果会导致投票毫无意义。将这些人放在同一个房间也不现实。iso可能会反对,参与者的不平衡会导致c的人处于极大的劣势。
04
c的兼容性不重要
如果你是c开发人员,那么肯定会把c视为一种简洁的编程语言。但对于我们其他人而言,c的印象完全不同。
c是通用的、跨语言的胶水,可以将一切紧密地结合在一起。
对于c++用户而言,c就是他们的api。从这一点来看,c的价值在于其简单性。请记住,c++关心的那一部分c是出现在接口(头文件)中的c。我们关心的是声明,而不是定义。c++需要调用c库中的函数(python、fortran、rust、d、java等语言也一样,在所有情况下都可以在接口边界使用c)。
因此,c是一种接口定义语言。向c添加的内容越多,定义接口就越困难。这些接口随着时间的推移保持稳定的可能性较小。
那么,c++中缺少是否重要?可能并不重要,因为这不太可能出现在公共接口中。
05
如今大家都在谈论c
过去,c的兼容性是c++的一大卖点。但如今,每个人(甚至他们的金鱼)都懂c。rust可以调用c函数,python、java、一切语言都可以!甚至怪异的javascript都可以在webassemby中调用c函数。
但是在这些语言中,接口是显式的。该语言提供的工具可以公开特定的c声明。当然,这比较麻烦。但这可以让接口非常非常清晰。而且还是有界的。例如,在rust中,调用c函数并不会迫使rust牺牲某些设计来容纳c子集。实际上c是被包含进去的。
mod confinment { use std::{c_char}; extern c { pub fn puts(txt: *const c_char); }}pub fn main() { unsafe { confinment::puts( std::new(hello, world!).expect(failed!).as_ptr() ); }}
06
编译器资源管理器
除非c的abi发生变化,否则这段代码可以一直正常运行。而且rust/c的边界非常清晰、不言自明。
因此,c++可能是为c兼容性付出最多的语言。
更糟糕的是,打开任何c的头文件,你很快就会发现一堆#ifdef __cplusplus。没错,c++的兼容性往往需要大量c开发人员的工作。兼容性一直是海市蜃楼。很多人都知道我的这条推文:
07
我们该何去何从?
我认为两个委员会都在尝试更多地沟通。他们计划明年在波特兰召开会议(尽管这个计划可能会变)。沟通是一件好事。
但是鸡同鸭讲的沟通效果会非常有限。两种语言的设计支柱可能都不协调。我会努力建议提供一个模板。但是首先我得吐槽c语言没有模块、没有命名空间,以及整个宏是什么玩意儿。
也许可以将c++能接受的c子集约束在c99上?也许两种语言都需要找到一个共同的子集并独立地发展?也许extern c需要影响解析。如果c++经历了多个时代,那么c可能是其中之一。
也许我们需要接受将c作为c++的子集,但唯一的方法是将wg14融入到wg21中。
现状可能不会改变。c++可能永远也无法从自己的起源中解脱,而c可能永远都要与那些顶着c语言之名的肮脏特性战斗。
台积电董事长否认苹果削减 5nm A14 芯片代工订单
20nm高性能开发套件现已发售!
鸿蒙 TabList 配合 Fraction 实现顶部切换效果演示
同步加速器光源可用于新世代电池开发
新基建浪潮下,AI的下一次突破何时到来?
浅谈C语言与C++的前世今生
微电网的两种拓扑架构思路介绍
阿里将成立名为“平头哥”的半导体公司!为什么是“平头哥”?
PCB差分信号设计中常见的误区详解
PLC网关助力传统非标设备实现物联网升级
工信部出手勒“马缰绳” 液晶面板踩刹车
NB-IoT射频指标到底是啥?一文讲清楚
80C51单片机的内部RAM简介
中国有无载人登月计划?国家航天局:先搞关键技术攻关
汽车safe检测仪的应用对于汽车安全提供了保障
关于云存储的一些优势分析
比特币扩容之争的困局是core开发组造成的吗?
荣耀9什么时候上市?荣耀9最新消息:小米华为仇视全部晋级,荣耀9正面应战小米6,网友们做不住了!
基于FPGA的LTE系统上行同步的实现
为下一代家用电器注入更多想象力