2008年11月11號(Single Day~~~)
C語言,在今天來說是一種特殊的編程語言遣妥。只有極少數(shù)人真的可以用C進行編程擅编,而且我們中很大一部分人都對C有自己的看法。緩沖區(qū)溢出燥透,棧溢出沙咏,整型數(shù)據(jù)溢出,C有很多廣為人知缺陷班套,而這些缺陷被人們隨意傳播肢藐,甚至那些不熟悉C的人們。我自己已經(jīng)有10年沒有接觸C了吱韭,由于這樣或那樣的原因吆豹。開始的額時候,編譯器是很昂貴的(在免費的UNIX被發(fā)布之前)而且很慢理盆,那時的環(huán)境是很糟的痘煤。而且,所有關(guān)于C的恐怖故事讓我覺得我這么一個小小的普通程序員怎么可以寫出可靠的C程序猿规。
撇過一些我直接從別的地方復制粘貼過來的很多小的C模塊不說衷快,我自己寫的第一個C程序是Converge VM。其中有兩件事情讓我驚呆了:-o 姨俩。第一蘸拔,寫C程序原來不是那么難。事后我才知道我年輕的時候浪費時間寫匯編代碼這件事在心理上給我了很大的支持环葵,畢竟C是高級一點的匯編語言调窍。一旦一個人理解了像指針(可以說是低級語言中最微妙的概念,因為真實世界中沒有相對應的比喻)這樣的概念张遭。第二件事情是邓萨,Converge VM沒有像我期待那樣滿是bug。
實際上菊卷,忽略可能在任何編程語言上都存在的邏輯錯誤缔恳,到目前為止在Converge VM中引發(fā)實際問題的只有兩個只針對C才會有的錯誤(主意,我肯定還有很多潛伏的bug洁闰,但是我情形還沒有碰上太多)褐耳。第一個錯誤是,一個list沒有以\0
(C中經(jīng)典的錯誤)渴庆,這個問題花了很長時間去調(diào)試铃芦。另一個錯誤則神奇的多了雅镊,花了我好幾個月時間。Converge 垃圾回收器可以謹慎地根據(jù)指針回收隨意分配的內(nèi)存空間刃滓。在所有的現(xiàn)在結(jié)構(gòu)中仁烹,指針都指的是字和字對齊的邊界。然而咧虎,已經(jīng)分配的內(nèi)存塊在長度上常常不是字和字對齊的卓缰。(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.) 所以有時候垃圾回收器會在一個內(nèi)存塊位置為4的地方嘗試讀取4bytes,即使那個內(nèi)存塊是5bytes長砰诵。換句話來說征唬,垃圾回收器嘗試讀入一塊數(shù)據(jù)的1bytes和內(nèi)存中理論上沒有權(quán)限的3bytes隨機數(shù)據(jù)。罕見和神奇的是茁彭,這導致的錯誤幾乎沒法解釋总寒。但是不夸張的說,在多少編程語言中一個人可以遞歸地加上垃圾回收器理肺?
我和Converge VM的經(jīng)歷不怎么不符合我之前的偏見摄闸。我已經(jīng)慢慢承認C程序會隨機出現(xiàn)segfault,丟數(shù)據(jù)妹萨,而且常常會像Vikings(維京海盜)去Lindisfarne一樣年枕。對比來看,用高級語言編寫的程序會按照正常邏輯和可以預料的模式報錯乎完。漸漸地熏兄,這些問題在我日常使用的我可以信任的這些用C寫的程序中,我都碰到了树姨。我不記得上次這些程序發(fā)生大問題的時候了霍弹。這些不會崩潰,也會優(yōu)雅的處理次要的錯誤娃弓。就算,我對這些軟件(我使用OpenBSD9年了岛宦,所以沒有比這些質(zhì)量更好的軟件了)極度挑剔台丛,還有一些明顯的原因以至于為什么它為什么如此可靠:它被很多人使用,而這些人幫助我們找出bug砾肺。軟件已經(jīng)被開發(fā)出來很長時間了挽霉,所以之前的版本都存在bug。并且变汪,坦誠一點侠坎,只有相當能干的程序員首選會傾向于C。但是裙盾,仍然存在一個根本的問題:為什么用C寫的程序堅如磐石实胸?
過了寫論文這段黑暗的時期之后他嫡,我最近做了一點C編程。對于長時間沒有著手寫C程序的人庐完,想妥妥地發(fā)封郵件都沒譜了钢属。這些年,我都是通過ssh
在遠程機器用sendmail
發(fā)送郵件的门躯。這解決了很多問題(比如黑名單)淆党,在很多網(wǎng)絡中它也有問題(尤其是無線網(wǎng)絡),一個過多的網(wǎng)絡連接會被拋掉讶凉。檢查郵件是否發(fā)送是很煩人的過程染乌。所以仔細檢查它的設計后,我打算寫一個簡單的工具集來穩(wěn)妥地通過ssh
發(fā)送郵件懂讯。最終的程序 - extsmail - 比我之前所期待的有更多的功能荷憋,但是最基礎的思想就是通過外部的命令比如ssh
簡單的重試發(fā)送郵件,直到成功發(fā)送域醇。我還想讓這個工具集盡可能占用資源少還實用台谊,還可移植。這必然決定應該用C寫extsmail譬挚。然后我決定嘗試盡可能地寫這個程序锅铅,就當是實驗吧。用傳統(tǒng)的UNIX方式减宣,只依賴可靠的UNIX分發(fā)版所提供的功能盐须,而且容錯能力強。在做的過程中漆腌,做了兩份關(guān)于用C寫程序的新手觀察資料贼邓。
第一個觀察不是太明顯。因為用C寫的程序有無數(shù)多種錯誤方式闷尿,我比平時更加細心塑径。特別的,任何內(nèi)存塊的的操作都會引發(fā)非常危險的緩沖溢出類型錯誤填具。然而统舀,在一個高級語言中,我可能會比較懶劳景,心想“嗯誉简,我索引數(shù)組的時候是不是應該給這個值減一?先跑一遍看看”盟广。在C中闷串,我會想“OK,坐下來想想原因”筋量。諷刺的是烹吵,跑程序和發(fā)現(xiàn)問題所花的時間和坐下來思考的時間是不一樣的碉熄,除了坐下來思考更加耗費腦力。
第二個觀察年叮,是我之前從沒有碰到過的具被,在C中沒有異常處理。如果只损,比如說extsmail一姿,要提高容錯能力,就得自己不得不處理所有可能的錯誤跃惫。從一方面來說叮叹,這是非常痛苦的,extsmail有很大一部分比例(大概40%)都在檢查和去除錯誤爆存,雖然UNIX系統(tǒng)方法已經(jīng)很仔細的處理出錯的情況了蛉顽。換句話說,當在C中調(diào)用一個方法先较,比如stat
的時候携冤,文檔會列出所有失敗的情況。用戶可以很容易的選擇應該在程序中修復哪個情況闲勺,哪些致命錯誤應該進一步處理(在extsmail中曾棕,內(nèi)存不足就是致命錯誤)。這就是在思維模式上基于語言的異常處理方式巨大的不同點菜循,經(jīng)典的哲學是正常地寫代碼翘地,僅在少數(shù)情況下寫try ... catch
語句塊來處理特定錯誤(很少碰到的錯誤)。Java癌幕,用受控制的異常衙耕,以不同的方式告訴用戶“當調(diào)用這個方法的時候,你需要try catch
特定的異成自叮”橙喘。
我明白了一件事情,當希望軟件足夠的強壯(魯棒性)的時候胶逢,基于異常的軟件設計是不合適的厅瞎。而需要明確的是知道一個方法返回的或者拋出的錯誤或者異常,然后根據(jù)情況去處理宪塔。在現(xiàn)在的IDE中,可以根據(jù)寫的方法自動顯示會拋出哪些異常囊拜,最多也就只能做到這一點了某筐。理論上,面向?qū)ο笾械淖宇惡投鄳B(tài)意味著預編譯的庫不能根據(jù)寫的代碼確定會不會拋異常(因為子類會重寫方法冠跷,會拋出不同的錯誤)南誊。從實用方面來說身诺,我懷疑這么多方法都會拋出很多不同的異常,這會讓用戶懵了抄囚。對比UNIX中的方法霉赡,就非常注意,它們會盡量減少返回給用戶的錯誤的數(shù)量幔托,和一些內(nèi)部錯誤穴亏,或者收集歸類錯誤。進一步重挑,我也懷疑那些依賴異常處理的很多庫需要大幅度的進行重寫來減少拋出的異常數(shù)量直到一個合理的數(shù)值嗓化。再進一步,方法的調(diào)用者應該決定什么錯誤應該次要的谬哀,可以恢復的刺覆,哪些會導致重要的問題,甚至導致程序結(jié)束;受控制的異常,被調(diào)用者強制處理的異常史煎,就忘了這一點谦屑。
Henry Spencer 說,“那些不懂UNIX的人注定要可憐地重新發(fā)明輪子”篇梭。這就是為什么這么多用C寫的程序比我們所提出的偏見更加堅固氢橙,UNIX文化,在計算機主流里很洋,最古老和最明智的文化充蓝,已經(jīng)發(fā)現(xiàn)很多把C的局限和缺陷變成優(yōu)勢的方法。就像我經(jīng)歷的喉磁,慢慢地我才明白了這點谓苟。綜上,如果沒有經(jīng)過大量的思考协怒,我不建議使用C涝焙。如果使用C,那最終的軟件堅如磐石孕暇,但開發(fā)會花費大量人力仑撞。
Source:http://tratt.net/laurie/blog/entries/how_can_c_programs_be_so_reliable