C語言,在今天來說是一種特殊的編程語言。只有極少數(shù)人真的可以用C進(jìn)行編程,而且我們中很大一部分人都對(duì)C有自己的看法。緩沖區(qū)溢出,棧溢出,整型數(shù)據(jù)溢出,C有很多廣為人知缺陷,而這些缺陷被人們隨意傳播,甚至那些不熟悉C的人們。我自己已經(jīng)有10念沒有接觸C了,由于這樣或那樣的原因。開始的額時(shí)候,編譯器是很昂貴的(在免費(fèi)的UNIX被發(fā)布之前)而且很慢,那時(shí)的環(huán)境是很糟的。而且,所有關(guān)于C的恐怖故事讓我覺得我這么一個(gè)小小的普通程序員怎么可以寫出可靠的C程序。
撇過一些我直接從別的地方復(fù)制粘貼過來的很多小的C模塊不說,我自己寫的第一個(gè)C程序是Converge?VM。其中有兩件事情讓我驚呆了:-o 。第一,寫C程序原來不是那么難。事后我才知道我年輕的時(shí)候浪費(fèi)時(shí)間寫匯編代碼這件事在心理上給我了很大的支持,畢竟C是高級(jí)一點(diǎn)的匯編語言。一旦一個(gè)人理解了像指針(可以說是低級(jí)語言中最微妙的概念,因?yàn)檎鎸?shí)世界中沒有相對(duì)應(yīng)的比喻)這樣的概念。第二件事情是,Converge VM沒有像我期待那樣滿是bug。
實(shí)際上,忽略可能在任何編程語言上都存在的邏輯錯(cuò)誤,到目前為止在Converge VM中引發(fā)實(shí)際問
題的只有兩個(gè)只針對(duì)C才會(huì)有的錯(cuò)誤(主意,我肯定還有很多潛伏的bug,但是我情形還沒有碰上太多)。第一個(gè)錯(cuò)誤是,一個(gè)list沒有以\0(C中經(jīng)典的錯(cuò)誤),這個(gè)問題花了很長(zhǎng)時(shí)間去調(diào)試。另一個(gè)錯(cuò)誤則神奇的多了,花了我好幾個(gè)月時(shí)間。Converge 垃圾回收器可以謹(jǐn)慎地根據(jù)指針回收隨意分配的內(nèi)存空間。在所有的現(xiàn)在結(jié)構(gòu)中,指針都指的是字和字對(duì)齊的邊界。然而,已經(jīng)分配的內(nèi)存塊在長(zhǎng)度上常常不是字和字對(duì)齊的。 (In all modern architectures, pointers have to live on word-aligned boundaries.However, malloc'd chunks of memory are often not word-aligned in length.) 所以有時(shí)候垃圾回收器會(huì)在一個(gè)內(nèi)存塊位置為4的地方嘗試讀取4bytes,即使那個(gè)內(nèi)存塊是5bytes長(zhǎng)。換句話來說,垃圾回收器嘗試讀入一塊數(shù)據(jù)的1bytes和內(nèi)存中理論上沒有權(quán)限的3bytes隨機(jī)數(shù)據(jù)。罕見和神奇的是,這導(dǎo)致的錯(cuò)誤幾乎沒法解釋。但是不夸張的說,在多少編程語言中一個(gè)人可以遞歸地加上垃圾回收器?
我和Converge VM的經(jīng)歷不怎么不符合我之前的偏見。我已經(jīng)
慢慢承認(rèn)C程序會(huì)隨機(jī)出現(xiàn)segfault,丟數(shù)據(jù),而且常常會(huì)像Vikings(維京海盜)去Lindisfarne一樣。對(duì)比來看,用高級(jí)語言編寫的程序會(huì)按照正常邏輯和可以預(yù)料的模式報(bào)錯(cuò)。漸漸地,這些問題在我日常使用的我可以信任的這些用C寫的程序中,我都碰到了。我不記得上次這些程序發(fā)生大問題的時(shí)候了。這些不會(huì)崩潰,也會(huì)優(yōu)雅的處理次要的錯(cuò)誤。就算,我對(duì)這些軟件(我使用OpenBSD9年了,所以沒有比這些質(zhì)量更好的軟件了)極度挑剔,還有一些明顯的原因以至于為什么它為什么如此可靠:它被很多人使用,而這些人幫助我們找出bug。軟件已經(jīng)被開發(fā)出來很長(zhǎng)時(shí)間了,所以之前的版本都存在bug。并且,坦誠(chéng)一點(diǎn),只有相當(dāng)能干的程序員首選會(huì)傾向于C。但是,仍然存在一個(gè)根本的問題:為什么用C寫的程序堅(jiān)如磐石?
過了寫論文這段黑暗的時(shí)期之后,我最近做了一點(diǎn)C編程。對(duì)于長(zhǎng)時(shí)間沒有著手寫C程序的人,想妥妥地發(fā)封郵件都沒譜了。這些年,我都是通過ssh在遠(yuǎn)程機(jī)器用sendmail發(fā)送郵件的。這解決了很多問題(比如黑名單),在很多網(wǎng)絡(luò)中它也有問題(尤其是無線網(wǎng)絡(luò)),一個(gè)過多的網(wǎng)絡(luò)連接會(huì)被拋掉。檢查郵件是否發(fā)送是很煩人的過程。所以仔細(xì)檢查它的設(shè)計(jì)后,我打算寫一個(gè)簡(jiǎn)單的工具集來穩(wěn)妥地通過ssh發(fā)送郵件。最終的程序 -?extsmail?- 比我之前所期待的有更多的功能,但是最基礎(chǔ)的思想就是通過外部的命令比如ssh簡(jiǎn)單的重試發(fā)送郵件,直到成功發(fā)送。我還想讓這個(gè)工具集盡可能占用資源少還實(shí)用,還可移植。這必然決定應(yīng)該用C寫extsmail。然后我決定嘗試盡可能地寫這個(gè)程序,就當(dāng)是實(shí)驗(yàn)吧。用傳統(tǒng)的UNIX方式,只依賴可靠的UNIX分發(fā)版所提供的功能,而且容錯(cuò)能力強(qiáng)。在做的過程中,做了兩份關(guān)于用C寫程序的新手觀察資料。
第一個(gè)觀察不是太明顯。因?yàn)橛肅寫的程序有無數(shù)多種錯(cuò)誤方式,我比平時(shí)更加細(xì)心。特別的,任何內(nèi)存塊的的操作都會(huì)引發(fā)非常危險(xiǎn)的緩沖溢出類型錯(cuò)誤。然而,在一個(gè)高級(jí)語言中,我可能會(huì)比較懶,心想“嗯,我索引數(shù)組的時(shí)候是不是應(yīng)該給這個(gè)值減一?先跑一遍看看”。在C中,我會(huì)想“OK,坐下來想想原因”。諷刺的是,跑程序和發(fā)現(xiàn)問題所花的時(shí)間和坐下來思考的時(shí)間是不一樣的,除了坐下來思考更加耗費(fèi)腦力。

第二個(gè)觀察,是我之前從沒有碰到過的,在C中沒有異常處理。如果,比如說extsmail,要提高容錯(cuò)能力,就得自己不得不處理所有可能的錯(cuò)誤。從一方面來說,這是非常痛苦的,extsmail有很大一部分比例(大概40%)都在檢查和去除錯(cuò)誤,雖然UNIX系統(tǒng)方法已經(jīng)很仔細(xì)的處理出錯(cuò)的情況了。換句話說,當(dāng)在C中調(diào)用一個(gè)方法,比如stat的時(shí)候,文檔會(huì)列出所有失敗的情況。用戶可以很容易的選擇應(yīng)該在程序中修復(fù)哪個(gè)情況,哪些致命錯(cuò)誤應(yīng)該進(jìn)一步處理(在extsmail中,內(nèi)存不足就是致命錯(cuò)誤)。這就是在思維模式上基于語言的異常處理方式巨大的不同點(diǎn),經(jīng)典的哲學(xué)是正常地寫代碼,僅在少數(shù)情況下寫try ... catch語句塊來處理特定錯(cuò)誤(很少碰到的錯(cuò)誤)。Java,用受控制的異常,以不同的方式告訴用戶“當(dāng)調(diào)用這個(gè)方法的時(shí)候,你需要try catch特定的異?!?。
我明白了一件事情,當(dāng)希望軟件足夠的強(qiáng)壯(魯棒性)的時(shí)候,基于異常的軟件設(shè)計(jì)是不合適的。而需要明確的是知道一個(gè)方法返回的或者拋出的錯(cuò)誤或者異常,然后根據(jù)情況去處理。在現(xiàn)在的IDE中,可以根據(jù)寫的方法自動(dòng)顯示會(huì)拋出哪些異常,最多也就只能做到這一點(diǎn)了。理論上,面向?qū)ο笾械淖宇惡投鄳B(tài)意味著預(yù)編譯的庫(kù)不能根據(jù)寫的代碼確定會(huì)不會(huì)拋異常(因?yàn)樽宇悤?huì)重寫方法,會(huì)拋出不同的錯(cuò)誤)。從實(shí)用方面來說,我懷疑這么多方法都會(huì)拋出很多不同的異常,這會(huì)讓用戶懵了。對(duì)比UNIX中的方法,就非常注意,它們會(huì)盡量減少返回給用戶的錯(cuò)誤的數(shù)量,和一些內(nèi)部錯(cuò)誤,或者收集歸類錯(cuò)誤。進(jìn)一步,我也懷疑那些依賴異常處理的很多庫(kù)需要大幅度的進(jìn)行重寫來減少拋出的異常數(shù)量直到一個(gè)合理的數(shù)值。再進(jìn)一步,方法的調(diào)用者應(yīng)該決定什么錯(cuò)誤應(yīng)該次要的,可以恢復(fù)的,哪些會(huì)導(dǎo)致重要的問題,甚至導(dǎo)致程序結(jié)束;受控制的異常,被調(diào)用者強(qiáng)制處理的異常,就忘了這一點(diǎn)。
如果有喜歡C/C++的小伙伴加一下裙:775356268一起學(xué)習(xí)交流。
Henry Spencer 說,“那些不懂UNIX的人注定要可憐地重新發(fā)明輪子”。這就是為什么這么多用C寫的程序比我們所提出的偏見更加堅(jiān)固,UNIX文化,在計(jì)算機(jī)主流里,最古老和最明智的文化,已經(jīng)發(fā)現(xiàn)很多把C的局限和缺陷變成優(yōu)勢(shì)的方法。就像我經(jīng)歷的,慢慢地我才明白了這點(diǎn)。綜上,如果沒有經(jīng)過大量的思考,我不建議使用C。如果使用C,那最終的軟件堅(jiān)如磐石,但開發(fā)會(huì)花費(fèi)大量人力。
本文章由網(wǎng)絡(luò)摘抄分享,如侵刪。