文/前橋和彌 html
在Donald C. Gause 和Gerald M. Weinberg 合著的《你的燈亮着嗎?》一書中,有這樣一節。 程序員
某計算機制造商開發了一種新型打印機。技術小組在如何保證打印精度的問題上很是苦惱,每次進行新的測試時,工程師都不得不花很長的時間測量打印機的輸出結果來追求精確性。
丹(Dan Daring)是這個小組中最年輕但或許是最聰明的工程師。 他發明了一種工具,即每隔8 英寸就在鋁條上嵌上小針。使用這個工具,能夠很快地找到打印機輸出位置的偏差。
這個發明顯著地提升了生產效率,丹的上司很是高興,提議給丹頒發一個公司的特別獎賞。他從車間裏拿了這個工具,帶回辦公室,這樣他寫報告的時候還能夠仔細地研究一下。
這個上司顯然還用不慣這個工具,當他把這個工具放在桌子上 的時候,將針尖朝上了。更不幸的是,當丹的上司的上司友好地坐 到桌角上,打算談談給丹頒發獎勵時,部門內的全部人都聽到了他 痛苦的尖叫聲——他的屁股上被紮了兩個相距8 英寸的孔。 編程
C 語言就恰如這個工具。也就是說,它是一門 數組
C 的發展歷程 編程語言
衆所周知,C 本來是爲了開發UNIX 操做系統而設計的語言。 工具
如此說來,好像C 應該比UNIX 更早問世,惋惜事實並不是如此,最先的 UNIX 是用匯編來寫的。由於厭倦了老是苦哈哈地使用匯編語言進行編程,UNIX 的開發者Ken Tompson 開發了一種稱爲「B」的語言。B 語言是1967 年劍橋大學的Martin Richard 開發的BCPL(Basic CPL)的精簡版本。BCPL 的前身是1963 年劍橋大學和倫敦大學共同研究開發的CPL(Combined Programming Lanugage) 語言。 測試
B 語言不直接生成機器碼,而是由編譯器生成棧式機(Stack Machine)用 的中間代碼,中間代碼經過解釋器(interpreter)執行(相似Java 和早期的 Pascal)。所以,B 語言的執行效率很是低,結果,在後來的UNIX 開發過程當中人們放棄了使用B 語言。 優化
在這以後的1971 年,Ken Tompson 的同事Dennis Ritchie 對B 語言作了改良,追加了char 數據類型,而且讓B 語言能夠直接生成PDP-11的機器代碼。曾經在很短的時間內,你們將這門語言稱爲NB(New B)。 spa
以後,NB 改稱爲C 語言——這就是C 語言的誕生。後來,主要是爲了知足使用UNIX 的程序員的須要,C 語言一邊接受來自各方面的建議,一邊摸着石頭過河般地進行着周而復始的功能擴展。1978 年出版了被稱爲C 語言寶典的The C Programming Language 一書。此書取了兩位做者(Brian Kernighan 和Dennis Ritchie)的姓氏首字母, 簡稱爲K&R。在後面提到的ANSI 標準制定以前,此書一直做爲C 語言語法 的參考書被人們普遍使用。據說這本書在最初發行的時候,Prentice-Hall 出版社制訂了對於當時存在 的130 個UNIX站點平均每一個能賣9 本的銷售計劃(相比Lift With UNIX[2])。固然了,哪怕是第一版K&R的銷售量,也以3 位數的數量級超過了 Prentice-Hall 出版社最初的銷售計劃 。本來只是像「丹的工具」同樣爲了知足 自用的C 語言,歷經坎坷,最終成爲全世界普遍使用的開發語言。 操作系統
順便提一下,經過http://www.cs.bell-labs.com/cm/cs/cbook/index.html能夠看到K&R 的網頁,各語種的K&R 封面排列在一塊兒,頗爲壯觀。
爲何存在奇怪的指針運算
若是試圖訪問數組的內容,老老實實地使用下標就能夠了。爲何存在指針運算這樣奇怪的功能呢?
其中的一個緣由就是受到了C的祖先B語言的影響。
B是一種「沒有類型」的語言。B中可使用的類型只有word 型(也就是整型),指針也是做爲整型來使用的(像浮點型這樣高級的事物,你根本見不到)。B 是虛擬機上運行的解釋器,這個虛擬機以word 爲單位分配內存地址(現在普通的計算機以字節爲單位)。因爲B以word 爲單位,若是指針(僅僅是表現地址的簡單的整數)加1,指針就指向數組的下一個元素。爲了繼承這種特性,C引入了「指針加1,指針前進它所指向類型的長度」這個規則。B 語言中一樣存在p[i]是 (p + i)的語法糖這樣的規則。但是,這裏的(p + i)只不過是單純的整數之間的加法運算。解引用*、地址運算符&,也以幾乎和C相同的形態存在於B語言中。另外還有一個理由就是,早先使用指針運算能夠寫出高效的程序。一般狀況下,咱們老是使用循環語句來處理數組,通常都寫成下面的形式,
for (i = 0; i < LOOP_MAX; i++) {
/*
* 在這裏,使用array[i]進行各類各樣的處理。
* array[i]會出現屢次。
*/
}
array[i]在循環中會出現屢次,每次都要進行至關於 array+i的加法運算,效率天然比較低。
所以,可使用指針運算重寫上面這段循環,
for (p = &array[0]; p != &array[LOOP_MAX],p++) {
/*
* 在這裏,使用*p進行各類各樣的處理。
* *p會出現屢次。
*/
}
儘管*p在循環內部會出現屢次,但加法運算只有在循環結束的時候執行一次。
K&R p.119 中敘述了「通常狀況下,使用指針的程序比較高效」。上面的說明應該能夠做爲這段敘述的根據吧。但是,這些不管怎樣都是老黃曆了。
現在,編譯器在不斷地被優化,對於循環內部重複出現的表達式的集中處理,是編譯器優化的基本內容。對於如今通常的C 編譯器,不管你使用數組仍是指針,效率上都不會出現明顯的差距。基本上都是輸出徹底相同的機器碼。
總的來講,C 的指針運算功能的出現,源自於早期的C自身沒有優化手段。這一點並不奇怪,請你們回想一下在前面介紹過的內容,C原本只是爲了解決開發現場的人們眼前的問題而出現的一種語言。Unix 以前的OS幾乎都是使用匯編寫的,即便晦澀難懂,人們也不會大驚小怪。對於當時的環境,追求什麼編譯器優化實在有點勉爲其難。所以,當初開發C 語言的時候,是徹底有必要提供指針運算功能的。但是……
不要濫用指針運算
被稱爲C語言寶典的K&R 指出:「通常狀況下,使用指針的程序比較高效。」這徹底是「那個時代的錯誤」。
但是,正如前面所說,對於現在的編譯器,不管是使用指針運算仍是下標運算,都生成幾乎徹底相同的執行代碼。
事到現在……難道不該該放棄使用指針運算,老老實實地使用下標訪問嗎?
雖然K&R 被不少人奉爲「神書」,但是對於我來講,它連做爲菜鳥實習的資料也未入流。爲何這麼說?由於在此書中,那些濫用指針的例程徹底可讓你崩潰。
莫名其妙地使用像*++args[0]這樣的語句,而且樂此不疲,實在讓人心煩。
K&R 裏面記載了下面這個做爲strcpy()實現的例子:
/* 將t拷貝成s;指針版3 */
void strcpy(char *s, char *t)
{
while (*s++ = *t++)
;
}
雖然乍一看不容易理解,可是這種寫法實際上是很是方便的。由於會在C 程序中常常遇到,因此咱們應該掌握這種慣用寫法。既然知道「乍一看不容易理解」,那就不該該這樣寫,難道不是嗎?
滿大街的C 語言入門書都在教育咱們,使用指針運算比使用下標會讓程序
☑更有效率
☑更有C 語言範兒
所謂的「更有效率」,只不過是臆想罷了。對於這種「微不足道的」優化工做,與其讓人去當心翼翼地作,還不如交給編譯器來幹。
所謂「更有C語言範兒」好像是有些道理。若是隻是爲了要讓程序「有範兒」,而讓代碼變得晦澀難懂,那麼仍是拜託你行行好,扔掉這種惡習吧。
在學校裏,咱們要完成一些課後做業。好不容易完成了一個使用下標的程序題,不料後面的那道題爲「請使用指針將剛纔那道題的程序從新完成一遍」。這種事常有吧。
老實說,這種事很無聊。也許你會很「威武」地依然使用下標原封不動
地把程序又寫了一遍,而後交給了老師。面對老師的指責,你義正辭嚴:
咦,下標運算符[]只不過是指針運算的語法糖而已,在本質上這樣的寫法也是在使用指針啊。
儘管這樣,這位可愛的老師可能仍是不會放過你,因而你就急了:
行,不就是把像p[i]這樣使用下標的地方,機械地一個個替換成* (p+i)嘛。
話說回來,丟了學分,我可不負責喲。在C的世界裏,使用指針運算要比使用下標的寫法讓人感受更「帥一些」。
可是……與其在這些無聊的地方「耍酷」,倒不如多花點時間學一些有用的知識。你要知道,做爲一個程序員,還有堆積如山的知識等着你去掌握呢。
固然,什麼樣的規則都有例外,好比,在「一個巨大的char 數組中,參雜了各類類型的數據①,而且咱們試圖讀取第多少字節的數據」這樣的狀況下,仍是使用指針運算寫的程序比較容易理解。
此外,做爲一個C程序員連指針運算的代碼也讀不懂,多少有點可悲。
儘管如此,讓咱們至少從如今開始儘可能使用下標來寫新的程序,這樣作對本身,以及對之後有機會閱讀你的程序的人,都有好處。
做者前橋和彌(Maebasi Kazuya),1969年出生,著有《完全掌握C語言》、《Java之謎和陷阱》、《本身設計編程語言》等。其一針見血的「毒舌」文風和對編程語言深入的見地受到廣大讀者的歡迎。
本文選自《征服C指針》一書,做者前橋和彌,吳雅明譯,由人民郵電出版社出版。