立即领取|华润集团、宁德核电、东风岚图等 20+ 标杆企业数字化人才培养实践案例 了解详情
写点什么

今时今日,C 还适合当下之所需么?

  • 2013-01-27
  • 本文字数:4636 字

    阅读完需:约 15 分钟

来自 Couchbase 的 Damien Katz 认为 C 依然是非常适合于后端编程的一门语言,然而有的开发者则觉得 C 有太多的瑕疵,他们支持 C++ 或是 Java,还有一些人连这两种语言也不喜欢。

在最近一篇题为 The Unreasonable Effectiveness of C 的博文中, CouchDB 的创建者 Damien Katz 表示 C 依然是非常适合于后端编程的一门语言,虽然现在已经有了很多更加现代化的语言,如 C++、Java、甚至是 Erlang 或是 Ruby,但他还是非常支持 C 语言。Katz 并不是认为 C 就是比其他任何语言都要好,但在“重点考虑性能与可靠性等场景下,C 是很难被打败的”——这段话援引自 Damien Katz 随后的一个帖子,旨在澄清自己的立场

虽然一开始使用 Erlang 编写了 CouchDB 的很多代码,但在花费了“2+ 个人月来处理 Erlang VM 中的一个崩溃问题”之后,Katz 感到非常不爽。

我们浪费了大量时间追踪核心 Erlang 实现中的一些问题,不敢保证发生什么以及原因,我们觉得也许问题出现在我们自己的插件 C 代码中,希望我们自己能够发现并修复问题。但事实并非如此,这是核心 Erlang 中的一个竞态条件 Bug。我们只能通过 Erlang 的代码查看器来找到这一问题。对于那些对计算机进行了过多抽象的编程语言来说,这是个很基本的问题。

出于这一点以及性能因素,Katz 决定逐步重写,“将 Couchbase 的代码换成 C,并将其作为大多数新特性的首选实现语言”。有趣的是,C 证明了“当我们遇到问题、调试与修复问题时,C 更具备可预测性。长远来看,C 的生产力更高”。

Katz 列出了对于后端来说,C 要优于更高层次语言如 C++、Java 等的若干原因:

  • 表现力——“C 的语法与语义非常强大且具有极强的表现力。凭借 C,我们既能预测高层算法,又能预测低层的硬件。它的语义非常简单,语法足够强大,能够极大降低认知上的负担,让程序员专注在重要的事情上”。
  • 简单——“C 是一种弱、静态类型语言,其类型系统非常简单。我们所说的弱最后会变成一个优点:C APIs 的“表面”是非常简单且小巧的。相对于大多数框架来说,C 的一个明显趋势与文化就是创建小型的库,对简单类型进行轻量级的抽象”。
  • 速度与内存使用——“C 是速度最快的语言,无论是部分还是完整的基准都表明了这一点。它不仅仅运行时是最快的,其内存使用与启动时间也是效率最高的。如果需要在空间与时间上进行折衷,那么 C 并不会对你隐藏任何细节信息,我们可以很容易地做出估计”。
  • 更快的开发周期——“对于开发者效率与生产力来说最为重要的就是‘构建、运行与调试’周期。周期越快,开发的交互性就越好,你就更容易处在任务的流态上。相对于所有主流的静态语言来说,C 拥有最为快速的开发交互性”。
  • 调试——“对于纯 C 代码来说,你可以查看调用堆栈、变量、参数、线程局部变量、全局变量,基本上可以查看到内存中的一切。这是非常有用的,特别是当你遇到了问题,这个问题在运行的服务器进程中出现了多日,并且无法重现的情况下更是如此。如果在更为高层的语言中失去了这个上下文,那么你就等着痛苦去吧”。
  • 跨平台——“有一个标准化的应用二进制接口(ABI),可为现有的所有操作系统、语言与平台所支持。它无需运行时,也不需要其他额外内容。这意味着你使用 C 所编写的代码并非仅仅可由 C 代码中的调用者所调用,还可以由现有的所有库、语言与环境所调用”。

Katz 也认为 C 有“很多瑕疵”:

没有范围检查,不小心就会导致内存出现问题、存在野指针与内存 / 资源泄露情况、对并发的附加支持、没有模块、没有命名空间。错误处理非常麻烦且冗长。很容易就会搞出一堆错误,调用堆栈也找不到了,恶意输入会控制你的进程。闭包?哈哈

Katz 对 C 的强烈偏爱源自突破 Couchbase 性能极限的需求以及调试问题(问题是 C 插件与 Erlang VM 联合使用所导致的)。他并不认为 C++、Go 或是 D 能够替换 C,但他认为 Rust 可能会成为“梦想之语言”,只要它能实现“类似于 C 的性能而且能够做到与 Erlang 安全的并发和内建的健壮性”。

Katz 的帖子在 Reddit Hacker News 上可谓是一石激起千层浪,有很多开发者谈到了 C 的优点,也有人建议其他语言。 robinei 加入到了字符串操作与错误检测激战当中:

我总想回到 C(从 C++ 等语言中),当我真的这么做了时,我发现不少地方通常都是很简单的,感觉真棒! 但接下来我需要进行字符串操作,或是这类笨拙的方法。

这时会出现很多分配操作,每一个都需要一个显式的 free 搞得我太痛苦了。我尝试通过 arena allocators 树来解决这个问题,就像 Go 中的 slice-strings,但最终 C 还是缺乏一些语法工具(命名空间前缀函数),这导致结果变得非常笨拙(将一切都分配到 arenas 中也是非常痛苦的)。

我发现由于要进行显式的错误检测,源代码文件长度增加了一倍(这种情况不常发生,但在诸如 sqlite 等一些库中,任何操作都有可能失败)。

还有很多方面导致我精疲力竭,我觉得非常不满意。

综上所述,我从哪儿来的还是回哪儿去吧,通常是 C++。

madhadron 提出了“更加现实的 C ”:

C 能够在 PDP-11 上很直接地编译成快速的机器码。 C 的标准库就是个笑话。它的缺点,特别是字符串相关的处理,是过去 40 年众多安全漏洞的罪魁祸首。

C 的工具根本不值得吹嘘,特别是与同辈的 Smalltalk 和 Lisp 相比。人们所使用的大多数 C 调试器都是命令行。根本没法和 Squeak 或是 Allegro Common Lisp 的标准调试器相比。

声明快速的 C 构建 / 调试 / 运行周期令人沮丧。表面上看起来很快,因为 C++ 在这个领域是失败的。如果你想知道如何加快构建 / 调试 / 运行周期,那么请看看 Turbo Pascal 吧。

你可以通过标准 ABI 在所有 Unix 上调用 C,虽然这么说没错,但原因却是因为 C 的普遍存在性而已。

geophile 对上述内容持不同看法

C/C++/Java,这是程序员视角的石头 / 剪刀 / 布。 多年以前,我从 C 开始,我发现自己用宏和库为声明与函数提供了很多很有用的组合。我发明了对象,同时也发现了 C++。

长久以来,我一直是个快乐的 C++ 用户,很早就开始了(还是 cfront 时代)。但我被语言的复杂性搞崩溃了,特别是特性之间微妙的交互,我厌倦了内存管理,渴望 Java,而它又适时地出现了。

我很开心。在学习语言时,我肯定我漏掉了某些东西。每个对象都在堆中么?真是如此么?真的没有办法将一个对象在物理上嵌入到另一个对象中么?但其他一切都很棒,我不介意这一点。

现在我在编写一些系统,这些系统会占用很多内存,包含成千上万的对象,有些对象很小,有些则很大。每个对象的代价快要搞死我了。GC 调优是个梦魇,我正在实现子分配模式。我编写了微基准,比较普通对象与序列化为字节数组的对象。由于 C++ 已经变得太恐怖了,比令我焦头烂额的早期版本还要复杂,因此我又渴望 C 了。

现在的我不再喜欢任何语言了。

对于某些人来说,C 看起来瑕疵太多,已经不适应现代的生产力要求了,但还有不少人依然能够很好地使用 C,尽管它有很多怪癖。开发者社区还是应该避免语言之争,而是更好地权衡每一种语言,根据项目需求与自身技能选择最合适的语言。毕竟,没有一种语言是完美的。

此文在 InfoQ 英文站也引来了众多读者的讨论,下面摘取部分读者的评论供大家参考。

读者 Mark Peskin 说到:

嗯,我喜欢 C。它很简单,没有 C++ 那些庞大且有瑕疵的面向对象特性。然而,使用 C 编写大型、可扩展、模块化的企业应用需要大量规则,这些规则在大型的软件企业中几乎是无法维护的。我认为 C 的一个问题是它会引诱聪明的开发者编写高度优化的代码,充满了 memcpy() 与指针运算,以此“打败编译器”,这对于其他开发者来说几乎无法理解(如果不信,你去读读 BSD 内核代码吧)。 总的来说,如果有很多开发者在开发大型、复杂且长期的项目,那么我认为你最好使用 Java(或是 Scala 等语言)。将 C 用在那些偶尔出现的场景中吧,这时你可能真的需要本地代码,使用基于消息的系统将二者集成起来(JNI 不行)。C++?还是算了吧。

读者 Josh Long 说到:

回应 Katz 关于 C 的问题。 我同意 Mark 的观点,在某种程度上,也认可 Damien 的看法。

Damien 提到的一点是我们很少在 C 中看到真正大型、全面的框架,比如 APIs。如果构建小型、某个方面的 API(通过一些 typedefs/structs 与函数作为“契约”),那么我们可以很轻松地将库“导出”并重用。我觉得这是 C 最适合之处。我从来不会因为性能问题而使用 C,但使用 C 实现某些功能则是更为轻松的事情(内核编程、嵌入式编程、处理硬件、所依赖的 APIs 并未在更高层次的语言如 Java、Ruby、Python 等进行过抽象的功能)。

我还尽量不使用 C 编写具有完整功能的系统,只是因为它对于大型项目来说不具备“可伸缩性”。使用 C 编写的大型项目最终的结果都是重新编写了很多东西(比如说对象与命名空间等)。恕我直言,真的没有多少领域需要从头到尾都用 C 不可。当然了,一些例外是系统级组件,比如说操作系统(Linux)或是 UI,如 GNOME。但对于应用来说,使用更高层次的语言,在高层次语言与平台之间存在缝隙之处再使用低层次 APIs 进行集成是更容易的做法。Java 存在很多这类“缝隙”,但随着 APIs 逐步成为很多不同操作系统上的常客后,在过去 10 年间,有些已经被逐步解决了:事件驱动的 IO、文件系统通知、文件权限与元数据等等。

Mark 为集成 C 库与模块提出了很好的解决方案。他认为 JNI 不行,建议使用消息。我是消息的忠实粉丝。本质来说,成功使用消息与成功使用 JNI 都需要同样的东西:你需要彻底简化导出 API。

在使用 JNI 时,绝不要将任何复杂的 C 类型“泄漏”到我的 Java API 中,反之亦然,并且总是通过数值类型与 char* -> jstrings 进行通信。即便我所公开的本地代码是用 C++ 编写的,我也依然会使用 C 风格的 JNI(而不是 C++),因为这种规范化有利于互操作。如果保持 C API 表面的简洁性,并且避免线程,那么通过 Java JNI、CPython 或是 MRI Ruby 等可以将其作为本地扩展。

一旦完成了这个过程,接下来通过消息公开 C API 就变得很简单了,因为根据定义,两个系统之间的消息负载不可能比 C 库还要复杂。当然了,如果使用消息,这意味着要么使用 C 编写消息代码,要么将 C 库公开到更高层的语言上,并在那里实现消息。消息好的一面是能够将高层语言代码与 C 代码进行隔离,这么做会比 Java 代码要薄一些。我依然不会直接将使用 C APIs 编写的代码链接到我的应用中。如果 C 代码挂掉了,那么消息系统就会接收请求,直到运行着 C 代码的另一个节点能够进行处理为止。另一方面,如果真的因为性能原因而使用 C,那么消息至少会引入一个网络传输,更不必说系统中的另一个组件了,这么做就抵消了使用 C 编码所带来的优势了。在这种情况下,我们可以编写稳定、行为良好的 JNI 或是本地扩展,但还是需要保持表面的小巧,并且理解前置与后置条件才行。没有线程。不要在 C 与 Java 之间传递指向复杂对象的指针。请确保你自己清楚谁来负责清理内存,什么时候清理。

总而言之,忘掉 C++ 吧。

读者 Bernd Kolb 说到:

或许你想要看看 mbeddr( mbeddr.com )。 mbeddr 旨在更好地支持嵌入式软件开发(但并不仅仅限于此),针对基于 C 语言与 IDE 的可扩展版本的小型与大型系统。现有扩展包括前置和后置的接口、组件、状态机与物理单元,以及对需求追踪和产品线变化的支持。基于这些抽象,mbeddr 还支持基于模型检测与 SMT 处理的形式验证。

通过这种方式,在大型项目与团队中,C 也可以做到“可伸缩”。此外,我们还可以引入现代的编程规则。通过扩展 mbeddr,你甚至可以为消息等添加基础信息。

查看英文原文: Is C Still A Suitable Language Today?

2013-01-27 05:555241
用户头像

发布了 88 篇内容, 共 262.2 次阅读, 收获喜欢 8 次。

关注

评论

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

看完了阿里大牛的Leetcode刷题笔记, 我成功拿到了字节跳动的offer

热爱java的分享家

Java 面试 算法 LeetCode 经验分享

Python Qt GUI设计:QSlider滑动条类(基础篇—16)

不脱发的程序猿

Python PyQt GUI设计 Qt Designer QSlider滑动条类

低代码实现探索(二)低代码中的数据

零道云-混合式低代码平台

低代码

简述以太坊P2P网络之UDP

devpoint

区块链 以太坊 udp 11月日更

API 编排的应用及痛点

全象云低代码

微服务 低代码 api 网关 API 编排

面试不慌,拿这70张思维导图,怒怼面试官

奔着腾讯去

c++ golang 数据结构 思维导图 TCP/IP

不是吧,都2021年了你别说你还不会Spring MVC基本应用

热爱java的分享家

Java 架构 程序人生 编程语言 经验分享

低代码实现探索(一)组件元信息定义

零道云-混合式低代码平台

低代码

规格模式(Specification Pattern)

Tom弹架构

Java 架构 设计模式

雇工模式(Employee Pattern)

Tom弹架构

Java 架构 设计模式

25 K8S之Endpoint对象

穿过生命散发芬芳

k8s 11月日更

Camtasia局部放大特效教程

淋雨

Camtasia 录屏

Flink CDC 2.1 正式发布,XTransfer技术专家贡献MongoDB CDC 连接器

XTransfer技术

大数据 实时计算

百度与赛诺菲签订许可协议,开启新一代mRNA药物和疫苗研发

百度大脑

人工智能 百度

按需引入ant-design-vue组件

石云升

Vue 11月日更

编写Java程序启动脚本最佳实践

WindFlying

对象池模式(Object Pool Pattern)

Tom弹架构

Java 架构 设计模式

空对象模式(Null Object Pattern)

Tom弹架构

Java 架构 设计模式

修复一个BaseRecyclerViewAdapterHelper漏洞

Changing Lin

11月日更

Flink Forward Asia 2021 延期,线上相见

Apache Flink

大数据 flink 编程 后端 实时计算

数据分析从零开始实战,Pandas读写CSV数据

老表

Python 数据分析 pandas 11月日更

Spring Boot的前世今生以及它和Spring Cloud的关系详解

Java高级开发

Java 架构 springboot SpringCloud

字节大牛把算法常见面试:哈希、链表、队列、递归全部总结出来了

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

阿里大牛最新公开压轴的“Redis深度笔记”,GitHub已标星81.6K

热爱java的分享家

Java 架构 面试 程序人生 编程语言

回顾“低代码”历史发展,是技术进步了还是倒退了?

优秀

低代码

TypeScript 之 Indexed Access Types

冴羽

JavaScript typescript html5 大前端 ES6

萝卜快跑:迎来首单业绩兑现,商业化试点服务进程进入新阶段

脑极体

终于有腾讯架构师把困扰我多年的《计算机网络原理》全部讲明白了

热爱java的分享家

Java 面试 编程语言 网络协议 经验分享

【高并发】浅谈AQS中的ReentrantLock、ReentrantReadWriteLock、StampedLock与Condition

冰河

Java 并发编程 多线程 高并发 异步编程

Go语言学习查缺补漏ing Day7

Regan Yue

Go 语言 11月日更

XTransfer 1号技术员工卡乐:从普通程序猿到技术专家

XTransfer技术

金融科技 支付 经验分享 创业公司

今时今日,C还适合当下之所需么?_Java_Abel Avram_InfoQ精选文章