C語言,在今天來講是一種特殊的編程語言。只有極少數人真的能夠用C進行編程,並且咱們中很大一部分人都對C有本身的見解。緩衝區溢出,棧溢出,整型數據溢出,C有不少廣爲人知缺陷,而這些缺陷被人們隨意傳播,甚至那些不熟悉C的人們。我本身已經有10念沒有接觸C了,因爲這樣或那樣的緣由。開始的額時候,編譯器是很昂貴的(在免費的UNIX被髮布以前)並且很慢,那時的環境是很糟的。並且,全部關於C的恐怖故事讓我以爲我這麼一個小小的普通程序員怎麼能夠寫出可靠的C程序。程序員
撇過一些我直接從別的地方複製粘貼過來的不少小的C模塊不說,我本身寫的第一個C程序是Converge VM。其中有兩件事情讓我驚呆了:-o 。第一,寫C程序原來不是那麼難。過後我才知道我年輕的時候浪費時間寫彙編代碼這件事在心理上給我了很大的支持,畢竟C是高級一點的彙編語言。一旦一我的理解了像指針(能夠說是低級語言中最微妙的概念,由於真實世界中沒有相對應的比喻)這樣的概念。第二件事情是,Converge VM沒有像我期待那樣盡是bug。編程
實際上,忽略可能在任何編程語言上都存在的邏輯錯誤,到目前爲止在Converge VM中引起實際問題的只有兩個只針對C纔會有的錯誤(主意,我確定還有不少潛伏的bug,可是我情形尚未碰上太多)。第一個錯誤是,一個list沒有以\0
(C中經典的錯誤),這個問題花了很長時間去調試。另外一個錯誤則神奇的多了,花了我好幾個月時間。Converge 垃圾回收器能夠謹慎地根據指針回收隨意分配的內存空間。在全部的如今結構中,指針都指的是字和字對齊的邊界。然而,已經分配的內存塊在長度上經常不是字和字對齊的。 (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.) 因此有時候垃圾回收器會在一個內存塊位置爲4的地方嘗試讀取4bytes,即便那個內存塊是5bytes長。換句話來講,垃圾回收器嘗試讀入一塊數據的1bytes和內存中理論上沒有權限的3bytes隨機數據。罕見和神奇的是,這致使的錯誤幾乎無法解釋。可是不誇張的說,在多少編程語言中一我的能夠遞歸地加上垃圾回收器?數組
我和Converge VM的經歷不怎麼不符合我以前的偏見。我已經慢慢認可C程序會隨機出現segfault,丟數據,並且經常會像Vikings(維京海盜)去Lindisfarne同樣。對比來看,用高級語言編寫的程序會按照正常邏輯和能夠預料的模式報錯。漸漸地,這些問題在我平常使用的我能夠信任的這些用C寫的程序中,我都碰到了。我不記得上次這些程序發生大問題的時候了。這些不會崩潰,也會優雅的處理次要的錯誤。就算,我對這些軟件(我使用OpenBSD9年了,因此沒有比這些質量更好的軟件了)極度挑剔,還有一些明顯的緣由以致於爲何它爲何如此可靠:它被不少人使用,而這些人幫助咱們找出bug。軟件已經被開發出來很長時間了,因此以前的版本都存在bug。而且,坦誠一點,只有至關能幹的程序員首選會傾向於C。可是,仍然存在一個根本的問題:爲何用C寫的程序堅如磐石?網絡
過了寫論文這段黑暗的時期以後,我最近作了一點C編程。對於長時間沒有着手寫C程序的人,想妥妥地發封郵件都沒譜了。這些年,我都是經過ssh
在遠程機器用sendmail
發送郵件的。這解決了不少問題(好比黑名單),在不少網絡中它也有問題(尤爲是無線網絡),一個過多的網絡鏈接會被拋掉。檢查郵件是否發送是很煩人的過程。因此仔細檢查它的設計後,我打算寫一個簡單的工具集來穩妥地經過ssh
發送郵件。最終的程序 - extsmail - 比我以前所期待的有更多的功能,可是最基礎的思想就是經過外部的命令好比ssh
簡單的重試發送郵件,直到成功發送。我還想讓這個工具集儘量佔用資源少還實用,還可移植。這必然決定應該用C寫extsmail。而後我決定嘗試儘量地寫這個程序,就當是實驗吧。用傳統的UNIX方式,只依賴可靠的UNIX分發版所提供的功能,並且容錯能力強。在作的過程當中,作了兩份關於用C寫程序的新手觀察資料。ssh
第一個觀察不是太明顯。由於用C寫的程序有無數多種錯誤方式,我比平時更加細心。特別的,任何內存塊的的操做都會引起很是危險的緩衝溢出類型錯誤。然而,在一個高級語言中,我可能會比較懶,心想「嗯,我索引數組的時候是否是應該給這個值減一?先跑一遍看看」。在C中,我會想「OK,坐下來想一想緣由」。諷刺的是,跑程序和發現問題所花的時間和坐下來思考的時間是不同的,除了坐下來思考更加耗費腦力。編程語言
第二個觀察,是我以前從沒有碰到過的,在C中沒有異常處理。若是,好比說extsmail,要提升容錯能力,就得本身不得不處理全部可能的錯誤。從一方面來講,這是很是痛苦的,extsmail有很大一部分比例(大概40%)都在檢查和去除錯誤,雖然UNIX系統方法已經很仔細的處理出錯的狀況了。換句話說,當在C中調用一個方法,好比stat
的時候,文檔會列出全部失敗的狀況。用戶能夠很容易的選擇應該在程序中修復哪一個狀況,哪些致命錯誤應該進一步處理(在extsmail中,內存不足就是致命錯誤)。這就是在思惟模式上基於語言的異常處理方式巨大的不一樣點,經典的哲學是正常地寫代碼,僅在少數狀況下寫try ... catch
語句塊來處理特定錯誤(不多碰到的錯誤)。Java,用受控制的異常,以不一樣的方式告訴用戶「當調用這個方法的時候,你須要try catch
特定的異常」。工具
我明白了一件事情,當但願軟件足夠的強壯(魯棒性)的時候,基於異常的軟件設計是不合適的。而須要明確的是知道一個方法返回的或者拋出的錯誤或者異常,而後根據狀況去處理。在如今的IDE中,能夠根據寫的方法自動顯示會拋出哪些異常,最多也就只能作到這一點了。理論上,面向對象中的子類和多態意味着預編譯的庫不能根據寫的代碼肯定會不會拋異常(由於子類會重寫方法,會拋出不一樣的錯誤)。從實用方面來講,我懷疑這麼多方法都會拋出不少不一樣的異常,這會讓用戶懵了。對比UNIX中的方法,就很是注意,它們會盡可能減小返回給用戶的錯誤的數量,和一些內部錯誤,或者收集歸類錯誤。進一步,我也懷疑那些依賴異常處理的不少庫須要大幅度的進行重寫來減小拋出的異常數量直到一個合理的數值。再進一步,方法的調用者應該決定什麼錯誤應該次要的,能夠恢復的,哪些會致使重要的問題,甚至致使程序結束;受控制的異常,被調用者強制處理的異常,就忘了這一點。.net
Henry Spencer 說,「那些不懂UNIX的人註定要可憐地從新發明輪子」。這就是爲何這麼多用C寫的程序比咱們所提出的偏見更加堅固,UNIX文化,在計算機主流裏,最古老和最明智的文化,已經發現不少把C的侷限和缺陷變成優點的方法。就像我經歷的,慢慢地我才明白了這點。綜上,若是沒有通過大量的思考,我不建議使用C。若是使用C,那最終的軟件堅如磐石,但開發會花費大量人力。設計
Source:http://tratt.net/laurie/blog/entries/how_can_c_programs_be_so_reliable指針