開源技術教學文件網 C 語言介紹

最後修改日期為 NOV 10, 2020

前言

在本章中,我們不急著動手寫程式。而會先對 C 語言做一些概念上的介紹。

為什麼要學 C 語言?

除了大專院校會把 C 語言當成教學工具外,C 語言和他的大兄弟 C++ 可說是資訊界最重要的兩個語言。許多重要的軟體專案是以 C 語言寫成:

許多高階語言的編譯器或直譯器也是以 C 寫成:

  • GCC:GNU 計劃的 C 和 C++ 編譯器
  • Perl:具有強大的文字處理能力的程式語言和命令列工具
  • Python:資料科學的第一把交椅
  • Ruby:Ruby on Rails 的母語
  • PHP:全世界約 78% 的網站使用 PHP 實作,包括 Facebook
  • Lua:最受歡迎的內嵌腳本語言

許多高階語言的延伸模組內部以 C 語言或 C++ 實作,再用該語言的 C API 做 binding。絕大部分的高階語言都可以用 C API 做 binding 寫延伸模組。即使平常少寫 C 語言,該語言的知識不會完全無用。

此外,在嵌入式系統中,由於系統資源受限,通常會用組合語言和/或 C 語言來寫程式,較少用 C++ 或其他的程式語言來實作。

在所有程式語言中,除了組合語言外,最貼近電腦硬體的語言就是 C 語言。對於學習電腦相關知識來說,學習 C 語言會比其他高階語言更有幫助。

雖然現在有一些新的系統語言,像是 D 語言或 Rust,C 語言並沒有因這些語言出現就衰退掉。至少現有的大型軟體專案不會因為新語言出現就全面重寫。這些新興系統語言只是實作新專案的選項之一,而非用來替代 C 的方案。

我們在市面上的基礎教材和大專院校中所學到的 C 語言算是入門級內容,和上述的重量級軟體專案有一大段落差。此外,一般人用的桌面系統 Windows 對 C 的支援不佳。所以會給人 C 語言不實用的錯覺。

C 語言是高階語言還是低階語言?

先說答案,C 語言是高階語言。

真正的低階語言是組合語言 (assembly language)。所謂的組合語言就是直接以處理器 (CPU) 的指令集 (instruction set) 來寫程式。由於每種處理器有自己獨特的指令集,故組語無法跨平台。

相較於組合語言,C 語言的語法無法直接和處理器的指令集做一對一的轉換,而要經過 C 編譯器將 C 程式碼轉換成等效的組語程式碼後,再由組語程式碼轉為機械碼。所以 C 語言是高階語言。

有些程式人會認定 C 語言是中階語言。實際上,沒有什麼所謂的中階語言。C 語言仍然是高階語言,和其他高階語言的差別只是 C 語言的抽象化程度比較少。由於抽象化程度較少,所以 C 程式碼在編譯時能夠轉換成體積小且效能佳的機械碼。

C 語言是跨平台語言

C 語言原本就是要用來撰寫 Unix 系統的系統語言。在 Unix 以 C 語言重新實作後,就具有跨平台的能力。當 Unix 要移植到新的硬體上時,只要重寫以組語撰寫的部分即可,以 C 語言撰寫的部分可以原封不動移到新的平台上。

但撰寫跨平台的 C 程式,無法像 Java、Golang 或其他高階語言那麼容易。因為 C 語言要直接面對異質的系統 C API。除了在語法及標準函式庫層次可自動跨平台之外,往往需要程式設計者費心安排程式碼,以寫出跨平台的 C 程式。筆者會在後續的文章撰寫和跨平台 C 程式相關的議題,並在此處更新連結。

C 語言的演進

有些人以為 C 語言是老古板,但其實 C 語言仍在持續演化。我們這裡會簡介到西元 2020 年為止的 C 標準演進過程。

K&R C

K&R C 是 C 語言尚未標準化前的非正式標準。該規格記載在 Brian Kernighan 和 Dennis Ritchie 所著的 C 語言經典教材 The C Programming Language 第一版中。由於 C 語言已經標準化了,不需再刻意追隨這個版本的 C 語言。

C89 或 ANSI C

C89 是第一個正式的 C 標準,許多人對 C 語言的印象就是基於這個版本的 C。由於絕大部分的 C 編譯器至少都支援 C89,有些很在意程式碼可攜性的軟體專案會刻意守在這個版本,像是 Lua。

C99

C99 是第一個 C 標準的重大改版,加入許多新的功能。包括但不限於:

  • 新增布林數 (boolean)
  • 新增複數 (complex number)
  • 新增 long long 整數
  • 新增以 // 開頭的單行註解
  • 可在 for 迴圈內初始化變數
  • 可用自動變數決定陣列長度
  • 新增一些函式庫

如果沒寫過 C 程式碼,可能會無法理解這些特性。先大略看過,學一陣子 C 語言自然會了解。

大抵上,C99 相容於 C89,但新增一些功能。

C11

C11 是第二個 C 標準的重大改版,加入許多新的功能。包括但不限於:

  • 新增型別安全的泛型程式,使用 _Generic 保留字
  • 支援多執行緒程式
  • 加入更多浮點數運算相關的巨集 (macro)
  • 支援匿名結構體 (structure) 和匿名聯合 (union)
  • 改善對 Unicode 的支援

同樣地,C11 大抵上相容於先前的 C 標準。

現代 C 語言 (modern C) 是指充份利用 C99 和 C11 的特性來撰寫 C 程式碼,不用刻意守在 ANSI C。善用現代 C 語言所帶來的特性,會讓程式碼更簡潔易讀。除非專案需要守在 ANSI C 或是所用的編譯器無法充分支援現代 C 語言的特性,我們應該善用現代 C 語言所帶來的便利性。

C17 或 C18

C18 是一個小改版,沒有引入新的語法特性,僅修復一些先前的問題。

C2x

C2x 是最新的 C 標準,這個版本將會引入一些新的特性。但這個版本還沒有穩定下來,所以,不用急著去追新。

Embedded C

初學 C 語言時,會預設 C 程式在個人電腦上運行。相對來說,embedded C 是用於嵌入式裝置的 C 標準,和一般 C 語言有些差異。剛學 C 語言時不用刻意學這套標準,等到熟悉 C 語言後再學也不遲。

POSIX

POSIX 不是 C 標準,而是類 Unix 系統的標準,用於 GNU/Linux 等系統。POSIX 中即定義了一套共通的 C API,所以 C 程式碼在不同類 Unix 系統間是可攜的。由於 GNU/Linux 等類 Unix 系統相當普遍,所以 POSIX 值得關注。

主要的 C 語言編譯器

語言規格只是一份技術性文件,規格上所列的特性是否能用,還得看 C 編譯器是否有實作。

本節來看目前市面上主要的 C 語言編譯器對 C 標準的支援情形。由於這裡的資訊會隨時間變動,建議最好查閱一下各編譯器的文件,不要只依賴本文的資訊。

Visual C++

Visual C++ 是隨附在 Visual Studio 內的 C 和 C++ 編譯器。由於 Visual Studio Community 裝好就有完整的 C 編譯器和 IDE (整合性開發環境),是很多人學習 C 語言時所用的工具。

原本 Visual C++ 對 C 標準的支援相對落後,最近 (西元 2020 年 11 月) 已經修正這個長期議題。現在 Visual C++ 支援最新的 C17 (出處)。

GCC

GCC 是 GNU/Linux 等類 Unix 系統所用的 C 和 C++ 編譯器,也有移植到 Windows 的版本。GCC 還蠻認真在支援 C 標準上,ANSI C、C99 和 C11 均有支援,而且可藉由參數指定 C 標準。

此外,GCC 自帶特有的 C extension,用來強化 C 語言的特性。要注意這些特性不是 C 標準的一部分,會使得 C 程式碼可攜性變差。如果已確認專案只會使用 GCC 來編譯,仍可考慮使用這些特性。

Clang

Clang 是 MacOS 預設的 C 和 C++ 編譯器,也有移植到 Windows 和類 Unix 系統上。Clang 刻意在參數上和 GCC 相容,但部分特性仍和 GCC 相異,故無法完全取代 GCC。Clang 有支援 ANSI C、C99 和 C11。

站在學習的觀點上,可以考慮用 Clang 來學習 C 語言。因為 Clang 吐出的錯誤訊息比 GCC 友善,對 C 語言初學者來說比較容易除錯。

其他 C 語言編譯器

除了上述通用型 C 編譯器,還有一些針對特定硬體所製作的 C 編譯器。我們在本節中介紹幾個常見的例子。

Intel C++ Compiler

顧名思義,Intel C++ Compiler 會對使用 Intel CPU 的電腦做程式碼優化。但對非 Intel CPU 的電腦就吃不到這些福利。根據 Intel 所發佈的調校數據,Intel C++ Compiler 所編譯出來的程式的確會比 GCC 或 Clang 所編譯的快一些。

Intel C++ Compiler 主要的特長是優化 C 程式碼,對於 C 語言學習者來說,不需要刻意用這套編譯器來練習 C 語言。如果對這套編譯器有興趣,Intel System Studio 可免費或付費使用,而 Intel Parallel Studio XE 則需付費使用。

CUDA

CUDA 是使用 NVIDIA 的顯示卡進行平行運算的技術。CUDA C 會在原本的 C 語言之外加入延伸語法,用來撰寫平行運算相關的程式碼。由於 CUDA 運算需搭配 NVIDIA 顯示卡,而且會用到非標準的 C 延伸語法,一開始不用刻意學 CUDA C。

Arduino

Arduino 是開放原始碼的單板電腦,程式人可使用 C 或 C++ 撰寫在 Arduino 上執行的程式。但 Arduino 程式是用 C 和 C++ 的子集來撰寫,和標準 C 有差異。除非對嵌入式系統有興趣,不需一開始就用 Arduino 學 C 語言。

和 C 語言相關的程式語言

C 語言簡潔的設計,間接影響了許多高階語言的特性。在這些高階語言中,C++ 和 Objective-C 直接和 C 語言相關,可視為 C 語言的超集合。由於 C 語言缺乏內建的物件系統,這兩個語言使用不同的物件系統來改善這項缺失。

C++

C++ 早期是以 C++ 程式碼轉 C 程式碼的轉譯器來實作,後來則變成實質的編譯器。在 C++ 演進的過程中,加入了許多特性,使得 C++ 成為兼具中階和高階特性的程式語言,相當龐大且複雜。

現在的 C++ 程式包含以下四種面向:

  • 命令式程式設計,這部分和 C 語言重疊
  • 物件導向程式設計
  • 泛型程式設計
  • 函數式程式設計

近年來,C++ 飛快地發展,和 C 語言差異越來越大。而且,C++ 並不是 C 語言的嚴格超集。因此,現在普遍認為,學 C++ 時不需先學 C

由於 C++ 兼容 C 的特性,在高階語法特性上又比 C 豐富得多,許多程式人在學完 C 之後,轉而學習和使用 C++。除了資源受限等不適合用 C++ 的情境,基本上 C 程式碼都可以用 C++ 改寫。

Objective-C

Objective-C 早期使用 C 前置處理器來撰寫,後來演化成實質的編譯器。在 Swift 問世前,Objective-C 是 Mac 家族系統主要的程式語言。目前為了支援現有的軟體,Mac 家族系統仍保留 Objecitve-C API。但由蘋果公司這幾年的發展趨勢來看,蘋果公司幾乎不會再為 Objective-C 開發新特性。

Objective-C 的語法是 C 語言的嚴格超集,但 Objective-C 所依賴的物件庫,目前只在 Mac 和 iOS 上的 Cocoa 有最完整的實作。在非 Mac 家族系統上,只能用 GNUstep 這套 Cocoa 的仿作品來替代。但 Cocoa 和 GNUstep 的 API 目前不完全相容。

此外,目前支援 Objective-C 的編譯器 GCC 和 Clang 兩者對 Objective-C 的特性不完全相容。有些新的特性只有在 Clang 上實作,但 GCC 沒有跟上來。所以,Objective-C 沒有辦法像 C 或 C++ 般,成為真正的通用型程式語言。在實務上,可將 Objective-C 視為 Mac 家族系統專用語言。

本系列文章的記述方式

對於完整的 C 程式碼,會以語法高亮來輔助閱讀:

#include <main>

int main(void)
{
    printf("Hello World\n");

    return 0;
}

同樣地,對於 C 程式碼片段,也會以語法高亮來輔助閱讀:

/* Excerpt */
assert(0 != strcmp("hello", "goodbye"));

Unix (含 macOS、GNU/Linux、FreeBSD 等) 終端機會以 $ 符號來表示指令提示符:

$ cd path/to/project

當使用 root 操作 Unix 終端機時,則會改用 # 來表示指令提示符:

# apt install gcc

Windows 終端機會以 > 來表示指令提示符。為了簡化,不顯示工作目錄:

> cd path\to\project

電子書籍

如果你覺得這篇 C 語言的技術文章對你有幫助,可以看看以下完整的 C 語言程式設計電子書:

現代 C 語言程式設計

分享本文
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
追蹤本站
Facebook Facebook Twitter