今时今日,C还适合当下之所需么?
本文來源于我在InfoQ中文站翻譯的文章,原文地址是:http://www.infoq.com/cn/news/2013/01/C-Language
來自Couchbase的Damien Katz認(rèn)為C依然是非常適合于后端編程的一門語言,然而有的開發(fā)者則覺得C有太多的瑕疵,他們支持C++或是Java,還有一些人連這兩種語言也不喜歡。
在最近一篇題為The Unreasonable Effectiveness of C的博文中,CouchDB的創(chuàng)建者Damien Katz表示C依然是非常適合于后端編程的一門語言,雖然現(xiàn)在已經(jīng)有了很多更加現(xiàn)代化的語言,如C++、Java、甚至是Erlang或是Ruby,但他還是非常支持C語言。Katz并不是認(rèn)為C就是比其他任何語言都要好,但在“重點(diǎn)考慮性能與可靠性等場景下,C是很難被打敗的”——這段話援引自Damien Katz隨后的一個(gè)帖子,旨在澄清自己的立場。
雖然一開始使用Erlang編寫了CouchDB的很多代碼,但在花費(fèi)了“2+個(gè)人月來處理Erlang VM中的一個(gè)崩潰問題”之后,Katz感到非常不爽。
我們浪費(fèi)了大量時(shí)間追蹤核心Erlang實(shí)現(xiàn)中的一些問題,不敢保證發(fā)生什么以及原因,我們覺得也許問題出現(xiàn)在我們自己的插件C代碼中,希望我們自己能夠發(fā)現(xiàn)并修復(fù)問題。但事實(shí)并非如此,這是核心Erlang中的一個(gè)競態(tài)條件Bug。我們只能通過Erlang的代碼查看器來找到這一問題。對于那些對計(jì)算機(jī)進(jìn)行了過多抽象的編程語言來說,這是個(gè)很基本的問題。出于這一點(diǎn)以及性能因素,Katz決定逐步重寫,“將Couchbase的代碼換成C,并將其作為大多數(shù)新特性的首選實(shí)現(xiàn)語言”。有趣的是,C證明了“當(dāng)我們遇到問題、調(diào)試與修復(fù)問題時(shí),C更具備可預(yù)測性。長遠(yuǎn)來看,C的生產(chǎn)力更高”。
Katz列出了對于后端來說,C要優(yōu)于更高層次語言如C++、Java等的若干原因:
- 表現(xiàn)力——“C的語法與語義非常強(qiáng)大且具有極強(qiáng)的表現(xiàn)力。憑借C,我們既能預(yù)測高層算法,又能預(yù)測低層的硬件。它的語義非常簡單,語法足夠強(qiáng)大,能夠極大降低認(rèn)知上的負(fù)擔(dān),讓程序員專注在重要的事情上”。
- 簡單——“C是一種弱、靜態(tài)類型語言,其類型系統(tǒng)非常簡單。我們所說的弱最后會變成一個(gè)優(yōu)點(diǎn):C APIs的“表面”是非常簡單且小巧的。相對于大多數(shù)框架來說,C的一個(gè)明顯趨勢與文化就是創(chuàng)建小型的庫,對簡單類型進(jìn)行輕量級的抽象”。
- 速度與內(nèi)存使用——“C是速度最快的語言,無論是部分還是完整的基準(zhǔn)都表明了這一點(diǎn)。它不僅僅運(yùn)行時(shí)是最快的,其內(nèi)存使用與啟動時(shí)間也是效率最高的。如果需要在空間與時(shí)間上進(jìn)行折衷,那么C并不會對你隱藏任何細(xì)節(jié)信息,我們可以很容易地做出估計(jì)”。
- 更快的開發(fā)周期——“對于開發(fā)者效率與生產(chǎn)力來說最為重要的就是‘構(gòu)建、運(yùn)行與調(diào)試’周期。周期越快,開發(fā)的交互性就越好,你就更容易處在任務(wù)的流態(tài)上。相對于所有主流的靜態(tài)語言來說,C擁有最為快速的開發(fā)交互性”。
- 調(diào)試——"對于純C代碼來說,你可以查看調(diào)用堆棧、變量、參數(shù)、線程局部變量、全局變量,基本上可以查看到內(nèi)存中的一切。這是非常有用的,特別是當(dāng)你遇到了問題,這個(gè)問題在運(yùn)行的服務(wù)器進(jìn)程中出現(xiàn)了多日,并且無法重現(xiàn)的情況下更是如此。如果在更為高層的語言中失去了這個(gè)上下文,那么你就等著痛苦去吧"。
- 跨平臺——“有一個(gè)標(biāo)準(zhǔn)化的應(yīng)用二進(jìn)制接口(ABI),可為現(xiàn)有的所有操作系統(tǒng)、語言與平臺所支持。它無需運(yùn)行時(shí),也不需要其他額外內(nèi)容。這意味著你使用C所編寫的代碼并非僅僅可由C代碼中的調(diào)用者所調(diào)用,還可以由現(xiàn)有的所有庫、語言與環(huán)境所調(diào)用”。
Katz也認(rèn)為C有“很多瑕疵”:
沒有范圍檢查,不小心就會導(dǎo)致內(nèi)存出現(xiàn)問題、存在野指針與內(nèi)存/資源泄露情況、對并發(fā)的附加支持、沒有模塊、沒有命名空間。錯誤處理非常麻煩且冗長。很容易就會搞出一堆錯誤,調(diào)用堆棧也找不到了,惡意輸入會控制你的進(jìn)程。閉包?哈哈Katz對C的強(qiáng)烈偏愛源自突破Couchbase性能極限的需求以及調(diào)試問題(問題是C插件與Erlang VM聯(lián)合使用所導(dǎo)致的)。他并不認(rèn)為C++、Go或是D能夠替換C,但他認(rèn)為Rust可能會成為“夢想之語言”,只要它能實(shí)現(xiàn)“類似于C的性能而且能夠做到與Erlang安全的并發(fā)和內(nèi)建的健壯性”。
Katz的帖子在Reddit與Hacker News上可謂是一石激起千層浪,有很多開發(fā)者談到了C的優(yōu)點(diǎn),也有人建議其他語言。robinei加入到了字符串操作與錯誤檢測激戰(zhàn)當(dāng)中:
我總想回到C(從C++等語言中),當(dāng)我真的這么做了時(shí),我發(fā)現(xiàn)不少地方通常都是很簡單的,感覺真棒!但接下來我需要進(jìn)行字符串操作,或是這類笨拙的方法。
這時(shí)會出現(xiàn)很多分配操作,每一個(gè)都需要一個(gè)顯式的free搞得我太痛苦了。我嘗試通過arena allocators樹來解決這個(gè)問題,就像Go中的slice-strings,但最終C還是缺乏一些語法工具(命名空間前綴函數(shù)),這導(dǎo)致結(jié)果變得非常笨拙(將一切都分配到arenas中也是非常痛苦的)。
我發(fā)現(xiàn)由于要進(jìn)行顯式的錯誤檢測,源代碼文件長度增加了一倍(這種情況不常發(fā)生,但在諸如sqlite等一些庫中,任何操作都有可能失敗)。
還有很多方面導(dǎo)致我精疲力竭,我覺得非常不滿意。
綜上所述,我從哪兒來的還是回哪兒去吧,通常是C++。
madhadron提出了“更加現(xiàn)實(shí)的C”:
C能夠在PDP-11上很直接地編譯成快速的機(jī)器碼。C的標(biāo)準(zhǔn)庫就是個(gè)笑話。它的缺點(diǎn),特別是字符串相關(guān)的處理,是過去40年眾多安全漏洞的罪魁禍?zhǔn)住?br />
C的工具根本不值得吹噓,特別是與同輩的Smalltalk和Lisp相比。人們所使用的大多數(shù)C調(diào)試器都是命令行。根本沒法和Squeak或是Allegro Common Lisp的標(biāo)準(zhǔn)調(diào)試器相比。
聲明快速的C構(gòu)建/調(diào)試/運(yùn)行周期令人沮喪。表面上看起來很快,因?yàn)镃++在這個(gè)領(lǐng)域是失敗的。如果你想知道如何加快構(gòu)建/調(diào)試/運(yùn)行周期,那么請看看Turbo Pascal吧。
你可以通過標(biāo)準(zhǔn)ABI在所有Unix上調(diào)用C,雖然這么說沒錯,但原因卻是因?yàn)镃的普遍存在性而已。
geophile對上述內(nèi)容持不同看法:
C/C++/Java,這是程序員視角的石頭/剪刀/布。多年以前,我從C開始,我發(fā)現(xiàn)自己用宏和庫為聲明與函數(shù)提供了很多很有用的組合。我發(fā)明了對象,同時(shí)也發(fā)現(xiàn)了C++。
長久以來,我一直是個(gè)快樂的C++用戶,很早就開始了(還是cfront時(shí)代)。但我被語言的復(fù)雜性搞崩潰了,特別是特性之間微妙的交互,我厭倦了內(nèi)存管理,渴望Java,而它又適時(shí)地出現(xiàn)了。
我很開心。在學(xué)習(xí)語言時(shí),我肯定我漏掉了某些東西。每個(gè)對象都在堆中么?真是如此么?真的沒有辦法將一個(gè)對象在物理上嵌入到另一個(gè)對象中么?但其他一切都很棒,我不介意這一點(diǎn)。
現(xiàn)在我在編寫一些系統(tǒng),這些系統(tǒng)會占用很多內(nèi)存,包含成千上萬的對象,有些對象很小,有些則很大。每個(gè)對象的代價(jià)快要搞死我了。GC調(diào)優(yōu)是個(gè)夢魘,我正在實(shí)現(xiàn)子分配模式。我編寫了微基準(zhǔn),比較普通對象與序列化為字節(jié)數(shù)組的對象。由于C++已經(jīng)變得太恐怖了,比令我焦頭爛額的早期版本還要復(fù)雜,因此我又渴望C了。
現(xiàn)在的我不再喜歡任何語言了。
對于某些人來說,C看起來瑕疵太多,已經(jīng)不適應(yīng)現(xiàn)代的生產(chǎn)力要求了,但還有不少人依然能夠很好地使用C,盡管它有很多怪癖。開發(fā)者社區(qū)還是應(yīng)該避免語言之爭,而是更好地權(quán)衡每一種語言,根據(jù)項(xiàng)目需求與自身技能選擇最合適的語言。畢竟,沒有一種語言是完美的。
此文在InfoQ英文站也引來了眾多讀者的討論,下面摘取部分讀者的評論供大家參考。
讀者M(jìn)ark Peskin說到:
嗯,我喜歡C。它很簡單,沒有C++那些龐大且有瑕疵的面向?qū)ο筇匦浴H欢?#xff0c;使用C編寫大型、可擴(kuò)展、模塊化的企業(yè)應(yīng)用需要大量規(guī)則,這些規(guī)則在大型的軟件企業(yè)中幾乎是無法維護(hù)的。我認(rèn)為C的一個(gè)問題是它會引誘聰明的開發(fā)者編寫高度優(yōu)化的代碼,充滿了memcpy()與指針運(yùn)算,以此“打敗編譯器”,這對于其他開發(fā)者來說幾乎無法理解(如果不信,你去讀讀BSD內(nèi)核代碼吧)。總的來說,如果有很多開發(fā)者在開發(fā)大型、復(fù)雜且長期的項(xiàng)目,那么我認(rèn)為你最好使用Java(或是Scala等語言)。將C用在那些偶爾出現(xiàn)的場景中吧,這時(shí)你可能真的需要本地代碼,使用基于消息的系統(tǒng)將二者集成起來(JNI不行)。C++?還是算了吧。
讀者Josh Long說到:
回應(yīng)Katz關(guān)于C的問題。我同意Mark的觀點(diǎn),在某種程度上,也認(rèn)可Damien的看法。
Damien提到的一點(diǎn)是我們很少在C中看到真正大型、全面的框架,比如APIs。如果構(gòu)建小型、某個(gè)方面的API(通過一些typedefs/structs與函數(shù)作為“契約”),那么我們可以很輕松地將庫“導(dǎo)出”并重用。我覺得這是C最適合之處。我從來不會因?yàn)樾阅軉栴}而使用C,但使用C實(shí)現(xiàn)某些功能則是更為輕松的事情(內(nèi)核編程、嵌入式編程、處理硬件、所依賴的APIs并未在更高層次的語言如Java、Ruby、Python等進(jìn)行過抽象的功能)。
我還盡量不使用C編寫具有完整功能的系統(tǒng),只是因?yàn)樗鼘τ诖笮晚?xiàng)目來說不具備“可伸縮性”。使用C編寫的大型項(xiàng)目最終的結(jié)果都是重新編寫了很多東西(比如說對象與命名空間等)。恕我直言,真的沒有多少領(lǐng)域需要從頭到尾都用C不可。當(dāng)然了,一些例外是系統(tǒng)級組件,比如說操作系統(tǒng)(Linux)或是UI,如GNOME。但對于應(yīng)用來說,使用更高層次的語言,在高層次語言與平臺之間存在縫隙之處再使用低層次APIs進(jìn)行集成是更容易的做法。Java存在很多這類“縫隙”,但隨著APIs逐步成為很多不同操作系統(tǒng)上的常客后,在過去10年間,有些已經(jīng)被逐步解決了:事件驅(qū)動的IO、文件系統(tǒng)通知、文件權(quán)限與元數(shù)據(jù)等等。
Mark為集成C庫與模塊提出了很好的解決方案。他認(rèn)為JNI不行,建議使用消息。我是消息的忠實(shí)粉絲。本質(zhì)來說,成功使用消息與成功使用JNI都需要同樣的東西:你需要徹底簡化導(dǎo)出API。
在使用JNI時(shí),絕不要將任何復(fù)雜的C類型“泄漏”到我的Java API中,反之亦然,并且總是通過數(shù)值類型與char* -> jstrings進(jìn)行通信。即便我所公開的本地代碼是用C++編寫的,我也依然會使用C風(fēng)格的JNI(而不是C++),因?yàn)檫@種規(guī)范化有利于互操作。如果保持C API表面的簡潔性,并且避免線程,那么通過Java JNI、CPython或是MRI Ruby等可以將其作為本地?cái)U(kuò)展。
一旦完成了這個(gè)過程,接下來通過消息公開C API就變得很簡單了,因?yàn)楦鶕?jù)定義,兩個(gè)系統(tǒng)之間的消息負(fù)載不可能比C庫還要復(fù)雜。當(dāng)然了,如果使用消息,這意味著要么使用C編寫消息代碼,要么將C庫公開到更高層的語言上,并在那里實(shí)現(xiàn)消息。消息好的一面是能夠?qū)⒏邔诱Z言代碼與C代碼進(jìn)行隔離,這么做會比Java代碼要薄一些。我依然不會直接將使用C APIs編寫的代碼鏈接到我的應(yīng)用中。如果C代碼掛掉了,那么消息系統(tǒng)就會接收請求,直到運(yùn)行著C代碼的另一個(gè)節(jié)點(diǎn)能夠進(jìn)行處理為止。另一方面,如果真的因?yàn)樾阅茉蚨褂肅,那么消息至少會引入一個(gè)網(wǎng)絡(luò)傳輸,更不必說系統(tǒng)中的另一個(gè)組件了,這么做就抵消了使用C編碼所帶來的優(yōu)勢了。在這種情況下,我們可以編寫穩(wěn)定、行為良好的JNI或是本地?cái)U(kuò)展,但還是需要保持表面的小巧,并且理解前置與后置條件才行。沒有線程。不要在C與Java之間傳遞指向復(fù)雜對象的指針。請確保你自己清楚誰來負(fù)責(zé)清理內(nèi)存,什么時(shí)候清理。
總而言之,忘掉C++吧。
讀者Bernd Kolb說到:
或許你想要看看mbeddr(mbeddr.com)。mbeddr旨在更好地支持嵌入式軟件開發(fā)(但并不僅僅限于此),針對基于C語言與IDE的可擴(kuò)展版本的小型與大型系統(tǒng)?,F(xiàn)有擴(kuò)展包括前置和后置的接口、組件、狀態(tài)機(jī)與物理單元,以及對需求追蹤和產(chǎn)品線變化的支持。基于這些抽象,mbeddr還支持基于模型檢測與SMT處理的形式驗(yàn)證。
通過這種方式,在大型項(xiàng)目與團(tuán)隊(duì)中,C也可以做到“可伸縮”。此外,我們還可以引入現(xiàn)代的編程規(guī)則。通過擴(kuò)展mbeddr,你甚至可以為消息等添加基礎(chǔ)信息。
查看英文原文:Is C Still A Suitable Language Today?
總結(jié)
以上是生活随笔為你收集整理的今时今日,C还适合当下之所需么?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于指针,可能是网上最详细的讲解了
- 下一篇: 代码区,初始化全局数据区,BSS,堆区,