天天看點

《C專家程式設計》一1.3 标準I/O庫和C預處理器

本節書摘來自異步社群《c專家程式設計》一書中的第1章,第1.3節,作者 【美】perter van der linde,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

c編譯器不曾實作的一些功能必須通過其他途徑實作。在c語言中,它們在運作時進行處理,既可以出現在應用程式代碼中,也可以出現在運作時函數庫(runtime library)中。在許多其他語言中,編譯器會植入一些代碼,隐式地調用運作時支援工具,這樣程式員就無須操心它們了。但在c語言中,絕大多數庫函數或輔助程式都需要顯式調用。例如,在c語言中(必要時),程式員必須管理動态記憶體的使用,建立各種大小的數組,測試數組邊界,并自己進行範圍檢測。

與此類似,c語言原先并沒有定義i/o,而是由庫函數提供。後來,這實際上成了标準機制。可移植的i/o由mike lesk編寫,最初出現在1972年左右,可在當時存在的3個平台上通用。實踐經驗表明,它的性能低于預期值。是以,人們對它又進行了優化和裁剪,後來成為标準i/o函數庫。

c預處理器大約也是在這個時候被加入的,倡議者是alan snyder。它所實作的3個主要功能是:

字元串替換:形式類似“把所有的foo替換為baz”,通常用于為常量提供一個符号名。

頭檔案包含(這是在bcpl中首創的):一般性的聲明可以被分離到頭檔案中,并且可以被許多源檔案使用。雖然約定采用“.h”作為頭檔案的擴充名,但在頭檔案和包含實作代碼的對象庫之間在命名上卻沒有相應的約定,這多少令人不快。

通用代碼模闆的擴充。與函數不同,宏(marco)在連續幾個調用中所接收的參數的類型可以不同(宏的實際參數隻是按照原樣輸出)。這個特性的加入比前兩個稍晚,而且多少顯得有些笨拙。在宏的擴充中,空格會對擴充的結果造成很大的影響。

被擴充為:

而:

則被擴充為:

它們所表示的意思風馬牛不相及。你可能會以為在宏裡面使用花括号就像在c語言的其他部分一樣,能把多條語句組合成一條複合語句,但實際上并非如此。

這裡對c語言的預處理器并不作太多的讨論。這反映了這樣一個觀點:對于宏這樣的預處理器,隻應該适量使用,是以無須深入讨論。c++在這方面引入了一些新的方法,使得預處理器幾乎無用武之地。

《C專家程式設計》一1.3 标準I/O庫和C預處理器

c并非algol

70年代後期,steve bourne在貝爾實驗室編寫unix第7版的shell(指令解釋器)時,決定采用c預處理器使c語言看上去更像algol-68。早年在英國劍橋大學時,steve曾編寫過一個algol-68編譯器。他發現如果代碼中有顯式的“結束語句”提示,諸如if ... fi或者case ... esac等,調試起來會更容易。steve認為僅僅一個“}”是不夠的,是以他建立了許多預處理定義:

這樣,他就可以像下面這樣編寫代碼:

再看一下相應的c代碼:

bourne shell的影響遠遠超出了貝爾實驗室的範圍,這也使得這種類似algol-68的c語言變型名聲大噪。但是,有些c程式員對此感到不滿。他們抱怨這種記法使别人難以維護代碼。時至今日,bsd 4.3 bourne shell(儲存于/bin/sh)依然是這種記法寫的。

我有一個特别的理由反對bourne shell,在我的書桌上堆滿了針對它的bug報告!我把它們發給sam,我們都發現了這樣的bug:這個shell不使用malloc,而是使用sbrk自行負責堆存儲的管理。在維護這類軟體時,每解決兩個問題通常又會引入一個新問題。steve解釋說他之是以采用這種特制的記憶體配置設定器,是為了提高字元串處理的效率,他從來不曾想到其他人會閱讀他的代碼。

bourne創立的這種c語言變型事實上促成了異想天開的國際c語言混亂代碼大賽(the international obfuscated c code competition),比賽要求參賽的程式員盡可能地編寫神秘而混亂的程式來壓倒對手(關于這個比賽,以後還有更詳盡的說明)。

繼續閱讀