本節書摘來自華章計算機《編寫高品質代碼:改善c程式代碼的125個建議》一書中的第1章,建議1,作者:馬 偉 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
資料是程式設計最基礎的概念,程式對資料進行操作。換句話說,任何一個完整的程式都可以看成是一組資料和作用于這組資料上的操作的說明。同時,程式中的每個資料項也都有一個與之相關的類型,稱為“資料類型”。
這樣,在程式中就可以使用資料類型來區分不同的資料,進而根據實際需要為這些資料配置設定不同的存儲空間。這就像成年人必須睡成人床,而給嬰兒配備嬰兒床就足夠了,如果你給嬰兒配置設定一張成人床就會造成資源浪費,相反給成年人配置設定一張嬰兒床則有可能會發生“溢出”。資料類型也一樣,由于不同的資料所需要的存儲容量各不相同,是以需要配置設定的記憶體空間大小也會不一樣,這樣才能夠保證記憶體資源的合理配置,使程式性能達到最優化。是以,如何合理、安全地使用這些資料類型是每個程式員必須掌握的。本章将圍繞這一話題進行讨論。
談到c語言的發展曆程,就不得不從最早的二進制語言說起。大家都知道,二進制語言可以說是世界上最早的計算機語言,它隻允許程式設計人員使用計算機能夠直接識别和執行的二進制代碼(即0和1,其中,0代表低電壓,1代表高電壓)來編寫程式。可想而知,這樣的編碼方式對程式設計人員來說是多麼困難與枯燥。是以,為了提高程式設計效率并減輕程式設計人員的負擔,是以後來很快便出現了彙編語言(assembly language)。
與二進制語言一樣,彙編語言也是面向機器的程式設計語言,不同類型的計算機上需要提供不同的彙編語言。但與二進制語言不同的是,彙編語言使用助記符(memoni)來代替操作碼,并用位址符号(symbol)或标号(label)來代替位址碼。由于彙編語言采用符号代替二進制代碼,是以,它也被稱為符号語言。使用彙編語言編寫的程式機器并不能直接識别,而需要通過一種程式将彙編語言翻譯成機器能夠識别的二進制語言,這種起翻譯作用的程式就是彙程式設計式。
相對于二進制語言,彙編語言不僅使開發效率得到了很大提升,而且它還具有許多優點,比如它能夠直接同計算機的底層軟體或硬體進行互動,直接通路與硬體相關的存儲器或 i/o端口;能夠不受編譯器的限制,對生成的二進制代碼進行完全控制;能夠對關鍵的代碼進行更準确地控制,避免因線程共同通路或硬體裝置共享而引起的死鎖;能夠根據特定的應用對代碼進行最佳優化,提高運作速度等。
盡管如此,彙編語言依舊是一種層次非常低的語言,它僅僅高于直接手工編寫二進制的機器指令碼。在實際應用中,它仍然暴露了一些不可避免的缺陷:如編寫的代碼非常難以閱讀,不好維護;很容易産生bug,難于調試;一般隻能針對特定的體系結構和處理器進行優化;開發效率很低,時間長且單調等。是以,我們更加需要一種設計描述簡單,能脫離對機型的要求,并且能在任何計算機上運作的計算機語言,我們稱這種語言為進階語言。這樣,程式設計人員就可以将問題及解決問題的算法過程描述出來,利用這種進階語言直接寫出各種表達式來描述簡單的計算過程,而無須針對不同的機型編寫不同的代碼。
進階語言編寫的程式稱為源程式,源程式不能在計算機上直接運作,必須将其翻譯成二進制代碼後才能執行。一般有兩種翻譯方式:一種是“解釋程式”方式,即将源程式作為輸入,翻譯一句後就送出計算機執行一句,這種方式并不形成目标程式;另一種是“編譯程式”方式,即将源程式作為輸入,全部翻譯成二進制代碼後再執行,編譯後的二進制程式稱為目标程式。
世界上出現的第一種進階語言是algol語言,它也可以算作c語言的前身。它和普通語言表達式非常接近,适用于數值計算,是以algol多用于科學計算機。1960年algol 60版本推出後,很受程式設計人員歡迎。algol 60推出了許多新的概念,如局部性概念、動态、遞歸、巴科斯-諾爾範式(backus-naur form,bnf)等。從某種意義上講,algol 60應該是程式設計語言發展史上的一個裡程碑,它标志着程式設計語言已成為一門獨立的科學學科,并為後來的軟體自動化及軟體可靠性的發展奠定了基礎。
雖然使用algol 60來描述算法很友善,但是它離計算機硬體系統卻很遠,不宜用來編寫系統程式。1963年英國劍橋大學在algol語言的基礎上增添了處理硬體的能力,并命名為“cpl”(combined programming language),即複合程式設計語言。但由于cpl的規模很大,學習和掌握都比較困難,是以沒有流行。1967年劍橋大學的martin richards對cpl語言進行了簡化,推出bcpl(basic combined programming language),即基本複合程式設計語言。它是典型的面向過程的進階語言,它的文法更加靠近機器本身,适合于開發精巧、高要求的應用程式,而且它對編譯器的要求也不高。同時,bcpl也是最早使用庫函數封裝基本輸入輸出的語言之一,這使得它的跨平台可移植性很好。
1969年,美國通用電氣公司、麻省理工學院與貝爾實驗室聯合建立了一個龐大的項目,命名為muktics工程。該項目的目的是建立一個作業系統,不過由于該項目過于複雜和龐大,最終失敗了。這也緻使項目的參與者之一通用電氣公司退出軟體領域,同時,貝爾實驗室的專家們也撤出了muktics工程,轉而研究新的領域。之後,貝爾實驗室的一位名為ken thompson研究員和他的同僚dennis ritchie組成一個非正式的小組,開始進行一些其他方面的研究。為了自娛自樂,ken thompson把他的“太空旅行”軟體移植到不太常用的pdp-7系統上。與此同時,ken thompson還為pdp-7系統編寫了一個簡單的作業系統。該作業系統比起muktics工程簡單了許多,采用彙編語言編寫,1970年brian kernighan為其取名為unix。
從這裡可以看出,著名的作業系統unix是早于c語言出現的,後來才用c語言重寫此系統,這一點一定要注意。
不過使用彙編語言編寫程式不僅吃力而且效率低下,是以ken thompson就考慮利用進階語言的特性來解決這一問題。1970年,ken thompson進一步簡化了bcpl,突出硬體的處理能力,并取“bcpl”的第一個字母“b”作為新語言的名稱,即b語言。同時,他還使用b語言編寫了unix作業系統程式。不過b語言還是存在許多問題,最大問題就在于無法表達不同的資料類型,而且效率不高,這也迫使ken thompson後來不得不在pdp-11的基礎上重新使用彙編語言來實作unix。面對b語言存在的問題,1971年dennis ritchie通過增加類型擴充了b語言,這次采用的是編譯模式而不是解釋模式,并且引入了類型系統,每個變量在使用前必須聲明。這種擴充的b語言稱為nb,即new b的縮寫。
1972年,ken thompson和dennis ritchie繼續對b語言進行完善和擴充,他們在保留b語言強大硬體處理能力的基礎上,擴充了資料類型,恢複了通用性,并取“bcpl”的第二個字母“c”作為新語言的名稱,即c語言。其實,c語言除了增加類型系統外,它還增加了許多友善編譯器設計者設計的新特性,主要表現在以下幾個方面:
數組下标從0開始,而不是從1開始。例如,我們定義一個數組arr[50],因為c語言的數組下标是從0開始的,是以它的合法範圍是arr[0]~arr[49]。是以,你不能夠向arr[50]裡存儲資料。
可以把數組看作指針,它簡化了參數的傳遞方法,使大家不必忍受傳遞一個數組到函數時需要複制所有數組内容的低效率。不過,值得注意的是,數組與指針并非在任何情況下都是等效的,這一點會在後面進行詳細闡述。
float類型被自動擴充為double類型。雖然在ansi c中情況不再如此,但最- 初浮點數常量的精度都是double類型的,所有表達式中float類型的變量總會被自動轉化為double類型。
增加register關鍵字,用此關鍵字告訴編譯器設計者哪些變量被放到了寄存器中,進而簡化編譯器,但卻也是以給程式員帶來了無窮無盡的麻煩,這一點會在後面的章節中詳細闡述。
此後,兩人又合作重寫了unix作業系統,c語言也伴随着unix作業系統成為一種廣受歡迎的計算機語言。圖1-1按時間順序闡述c語言的由來。

https://yqfile.alicdn.com/97fe12ceda341080e5a3a4f8839ee8685e3e0d3a.png" >
1978年,為了讓c語言脫離unix作業系統,成為任何計算機上都能運作的通用計算機語言,brian kernighan和dennis ritchie共同撰寫了《the c programming language》的第1版,該著作簡稱為“k&r”。書中對c語言的文法進行了規範化描述,書末的參考指南則給出了當時c語言的完整定義,這也成為當時c語言事實上的标準,此标準稱為“k&r c”。從此以後,c語言被移植到各種機型上,并受到廣泛支援。
随着c語言在多個領域的推廣和應用,一些新的特性不斷被各種編譯器實作并添加進來。于是建立一個新的“無歧義、與具體平台無關的c語言定義”就成為越來越重要的事情。1983年,美國國家标準委員會ansi(american national standards institute)屬下專門負責資訊技術标準化的機構asc x3(現已更名為國際資訊技術标準委員會(international committee for information technology standards,incits))成立了一個專門的技術委員會 j11(j11 是委員會編号,全稱是x3j11),用于起草關于c語言的标準草案。1989年,ansi正式通過c語言标準草案,至此該标準成為美國國家标準,此标準也稱為c89标準。
随後,《the c programming language》第2版出版發行,書中内容根據 ansi c(c89)進行了更新。1990年,在iso/iec jtc1/sc22/wg14(即iso/iec聯合技術第i委員會第22分委員會第14工作組)的努力下,iso準許ansi c成為國際标準,于是iso c(又稱為c90)誕生。與c89相比,c90除了标準文檔的印刷編排細節有些不同外(主要表現在删除了“rationale”一節,并把文檔的格式與段落編碼作了改動),它們在技術上是完全一樣的。到目前為止,c89是c語言運用得最廣泛的标準,基本上所有的c語言編譯器都完全支援該标準。相對于“k&r c”,c89主要做了以下幾方面的改進:
增加了新特性——原型。原型是函數聲明的擴充,它使得編譯器很容易根據函數的定義檢查函數的用法。
增加了一些新的關鍵字,如enum、const、volatile、signed與void。c89的關鍵字見表1-1。
除此之外,c89還做了許多其他的改進,如增強了預處理指令,定義了相關的宏,允許将結構本身作為參數傳遞給函數,從“無符号保留”轉到“值保留”等。
自iso c(c90)推出之後,iso又于1994年與1996年分别出版了c90的技術勘誤文檔,更正了一些印刷錯誤,同時,在1995年還通過了一份c90的技術補充,這份補充對c90進行了微小擴充,擴充後的iso c被稱為c95。
1999年,ansi和iso又通過了最新版本的c語言标準和技術勘誤文檔,該标準被稱為c99。這裡需要說明的是,與c89不同,并非市面上所有的編譯器都支援c99,并且有的編譯器隻支援c99的部分新特性。相對于c89,c99主要做了以下幾方面的改進:
增加了restrict與inline關鍵字。
新增_bool、_complex與_imaginary 3種資料類型,如c99中定義的複數類型為:float_complex、float_imaginary、double_complex、double_imaginary、long double_complex與long double_imaginary。
增強數組的功能,支援可變長數組等。
支援複合指派。
增強預處理程式,如引入_pragma運算符,并增加了一些内部宏等。
支援柔性數組結構成員,即允許結構中的最後一個元素是未知大小的數組。
由于技術的發展日新月異,是以雖然c99還沒有得到完全支援,但在2007年,标準委員會就又開始起草新的c語言标準來取代現有的c99标準,該标準命名為c1x,c1x是一個非正式名字。2011年12月,ansi正式采納了iso/iec 9899:2011标準,即c11标準。相對于c99,c11主要做了如下幾方面的改進:
采用新的對齊規範,包括_alignas說明符、_alignof運算符、aligned_alloc函數與頭檔案。
增加_noreturn函數标記。
增加_generic關鍵詞。
增加靜态斷言_static_assert()。
删除gets()函數,c99中已經将此函數标記為過時,推薦新的替代函數gets_s()。
采用新的fopen()模式。
增加匿名結構體/聯合體。
支援多線程技術,包括_thread_local與頭檔案。
增加_atomic類型修飾符和頭檔案。
帶邊界檢查(bounds-checking)的函數接口,定義了新的安全的函數,例如fopen_s()、strcat_s()等。
改進unicode支援與頭檔案。
增加quick_exit()函數作為第三種終止程式的方式。
可以建立複數的宏。
增加更多處理浮點數的宏。
struct timespec成為time.h的一部分,以及宏time_utc和函數timespec_get()。
綜上所述,可以用圖1-2來直覺地闡述c語言标準的發展曆程。

https://yqfile.alicdn.com/d050768707ba2e90d060740fbeb80d17abeb878d.png" >
在gcc編譯器中,針對不同版本的c語言标準,可以通過在指令行中使用“-std”選項來選擇所需要使用的c語言标準版本。
1)c89或者c90
2)c95
3)c99
4)c11
5)除此之外,如果需要在gcc中使用c擴充,還可以通過如下參數形式實作: