對於C語言,你停留在哪個版本呢?K&R C?ANSI C還是ISO C?以前想學C時,或者現在開始想學C時,別人會建議你看哪本書呢?K&R的《The C Programming Language》?是的!這是經典好書,只是在這本之後,你腦袋的C更新了嗎?

C的歷史更迭

身為程式設計者,無論是從哪個語言開始入門,隨著能力與經驗的增長,或多或少地,都會聽過或甚至遇到得接觸C語言的場合,原因不需要在這邊贅述,在網路上,可以找到一萬個要學習C語言的理由,我的第一個程式語言就是C語言,儘管當初學得不好,現在工作上常用到的語言,也不是它,然而,每隔一段時間去回顧一下C語言,仍舊會帶來不少的啟發與收穫。

由於日前玩了一陣子Arduino開發板,後來接觸Go語言,而最近在玩Python時,想瞭解一下如何使用Python包裝C程式庫,這些都免不了都要回顧一下C語言。

在我的印象中存在著「相較於現代的其他語言,C的語法集相對來說已經是少了,複習一下並不難」,於是,打開了我留下的文件,然而,在回顧與練習其中程式碼的同時,編譯器很認真地給了我一堆警訊,就像是在大聲地對我嚷嚷著「你的C到底是停在哪個年代了」。

是啊?停在哪一個年代了呢?C語言的首次發展,是在1969年到1973年之間,而K&R的《The C Programming Language》被公認為非正式C語言標準的時間,是在我出生後的三年;1989年C語言被美國國家標準協會(ANSI)標準化,這個版本被稱為C89,也就是我在學校時,初次學習C語言的版本;1990年國際標準化組織(ISO)對ANSI C做了些許修改,規範了國際標準的C語言,這個版本又被稱為C90。

印象中出社會後看的幾本C語言書籍,都是以C90為主,就算是近幾年偶而翻閱市面上的新書,也多半沒有太多變化,對我來說,C語言彷彿是被時光凍結了,直到最近翻閱了《21世紀新語言》第二部份有關語言的內容,才突然有了被解凍的感覺。

在ANSI/ISO的標準確立後,C語言的規範確實有段時間沒有大的變動,直到1999年ISO發表了被稱為C99的規範,ANSI於隔年2000年採用了此標準,不過,這個版本,並沒有引起Microsoft或Borland等大廠商的興趣,而開放原始碼界大老們也覺得尚不成熟,因而未被廣泛接受。

這段時間如果不是以C語言為主要工作語言的人,腦中的C,應該都是處於未更新的狀況。

到了2011年12月8日,ISO正式發布了新的C語言的新標準C11,其中,刪除了gets()函式,這也就是日前回顧C語言之時,編譯器會塞給我一堆警訊的原因之一。

啟動腦袋更新的一些特性

如果想知道C99、C11有哪些新特性,維基百科上〈C語言〉條目就條列了許多特性,當然,想要全面瞭解而不要有其他誤差資訊的話,最好的方式是閱讀ISO 9899:1999與ISO/IEC 9899:2011規格書,不過,想要快速地更新一些實用特性的話,可參考《21世紀新語言》第二部份有關語言的內容。

例如,main函式不需要特別加上return了,從C99開始,若最後沒加上return 0,編譯器就會這麼假設;宣告變數也不再像C89那樣,必須在區塊的開頭,例如,現在可以在for迴圈的宣告區宣告變數,這可以減輕程式碼撰寫時維護變數的負擔;如果需要動態大小的陣列,也不一定要使用malloc,C99之後已經可以根據變數來決定陣列大小了。

若想進一步地瞭解C99的其他特性,可參考《C programming: A Modern Approach, 2nd Edition》,其中針對C99的特性部份,有特別標示出來,例如,陣列初始時,Designated initializers的特性,可以指定特定索引處元素之值,其他未指定部份給予預設值,像是int a[15] = {[2] = 29, [9] = 7, [14] = 48};這行,會將索引2設為29、9設為7、14設為48,其他索引處預設為0。

C99支援了long long、long double _Complex、float _Complex等類型,如果含括(include)stdint.h,可以使用明確長度的整數類型,像是int8_t、int16_t、int32_t、uint64_t等,這讓我聯想到Go語言中int8、int16、int32、uint64等預定義型態,隨著被更新的知識增加,感覺腦袋中對C語言的印象,也開始有了現代感。

gcc與clang

既然對C語言也開始有了現代感,那麼,它有沒有……嗯……lambda、closure之類的東西呢?

C語言的函式指標,雖然可以做到現代語言一級函式中的一些效果,然而,使用上並不是那麼方便,我在搜尋這類特性的時候,找到了相關的文件談到,可以在函式中宣告函式,而這個特性不錯,雖然無法建立匿名函式,不過,搭配了函式指標之後,確實可以模擬closure的一些效果,像是將目前函式中的一些變數,帶到另一個被呼叫的函式之中,而不用透過參數來傳遞。

不過,這是gcc的擴充功能,並不是C99或C11的一部份,如果想瞭解這類的擴充特性,在Linux C程式設計的這類書籍中,會比較常見到,而根據我搜尋到的文件指出,如果想要瞭解Linux核心程式碼,對這類擴充就有熟悉的必要性。

雖然開發者多半知道有gcc,然而,就目前這個時間點來看,gcc有個強大對手——clang。這個專案是2005年由蘋果電腦發起,目前亦是主流編譯器之一,FreeBSD 10就將預設的編譯器從gcc換成clang。在瞭解clang特性的過程中,block這個擴充語法吸引了我,使用起來就像是匿名函式,不過,變數生命周期的行為上,並沒有closure的功能,然而,可以實作類似以下的foreach程式碼:

foreach(^(int n) {
    printf("%d\n", n);
}, arr, 5);

儘管有時標準走在實作之前,而有時實作會走在標準之前,然而使用了擴充語法,就意味著得注意相容性,除非你非常熟悉C語言的規格,否則在分辨哪個版本支援的語法或擴充,有時是個困難。

藉由工具會是比較好的方式,例如,gcc可以透過-std來指定使用c89、c99甚至c11標準,使用-W、-Wall(all -W options)來產生必要的警訊,如果想要更嚴格地遵守標準,可以指定-pedantic,這可以令編譯器拒絕非標準特性。

讓腦袋裝入現代原則

在這篇〈How to C in 2016〉文件中,一開始就說的很好:「如果能避免的話,C的首要原則就是不要寫C,如果一定要寫C,務必遵守現代原則。」文中也指出,許多人在不同的時期學習C,然後,知識就停滯不前了,然而,最重要的是,別讓C的開發,停在80/90年代學到的東西。

正如《C Programming: A Modern Approach, 2nd Edition》中寫到的,在現在這個年代,想要有效率地使用C語言,可以學習的有:如何避免語言缺陷、善用相關工具、利用現代的程式庫、採用適當的慣例、使用標準C而不是傳統的C、注意到可移植性的問題等。

如果你曾經學習過C,而且好一陣子沒用了,記得更新一下腦袋,如果你正想著下一門語言該學些什麼,接觸C是個不錯的選擇,此時,記得剛剛這幾個建議,現在已經是2016了,學些有現代感的C吧!因為,無論你是這兩種情況中哪一個,必然都有許多的啟發!

專欄作者

熱門新聞

Advertisement