C語言

通用編程語言

C語言(英語:C Language)是一種通用的、程序式編程程式語言,支援結構化編程、詞法作用域和遞迴,使用靜態型別系統,並且廣泛用於系統軟體應用軟體的開發。

C語言
在第一部介紹C語言的書籍《C程式設計語言》的初版封面上使用的C標識
編程範型程序式指令式編程程序式)、結構化編程
設計者丹尼斯·里奇(Dennis Ritchie)
實作者丹尼斯·里奇(Dennis Ritchie)和肯·湯普遜(Ken Thompson)
面市時間1972年,​52年前​(1972
目前版本
  • C17(2018年6月;穩定版本)
  • C23(2020年12月11日;預覽版本)[1][2]
編輯維基數據鏈結
型態系統靜態, 弱型別, 明示英語Manifest typing, 名稱英語Nominal type system
作業系統跨平台
網站www.iso.org/standard/74528.html 編輯維基數據鏈結
主要實作產品
ClangGCCMSVCTurbo CWatcom C
啟發語言
BBCPLCPL)、ALGOL 68[3]組合語言PL/IFORTRAN
影響語言
大量
如:awkBitC英語BitCcshC++C#
DJavaJavaScriptObjective-CPerlPHPRust

C語言於1969年至1973年間,為了移植與開發UNIX作業系統,由丹尼斯·里奇肯·湯普遜,以B語言為基礎,在貝爾實驗室設計、開發出來。二十世紀八十年代,C語言應用日漸廣泛。為了避免各開發廠商用的C語言的語法產生差異,美國國家標準局為C語言訂定了一套完整的國際標準語法,稱為ANSI C,作為C語言的標準。與此同時,國際標準化組織也接受該標準為國際標準。因此,ANSI C也同時被稱為ISO C。二十世紀八十年代至今的有關程式開發工具,一般都支援符合ANSI C的語法。

C語言具有高效、靈活、功能豐富、表達力強和較高的可移植性等特點,在程式設計中備受青睞,成為最近25年使用最為廣泛的程式語言[4]。目前,C語言編譯器普遍存在於各種不同的作業系統中,例如Microsoft WindowsmacOSLinuxUnix等。C語言的設計影響了眾多後來的程式語言,例如C++Objective-CJavaC#等。現行的許多軟體都是由C語言或者其影響和衍生的程式語言開發出來的。

概述

ALGOL一族的大多數程序式程式語言類似,C語言是一個有結構化程式設計、具有變數作用域(variable scope)以及遞迴功能的程序式語言。其採用的靜態型別系統可以防止無意的程式設計操作。C語言中所有的可執行代碼都被包含在子程式(函式)裡。其傳遞參數均是以值傳遞(pass by value)[5],另外也可以傳遞指標(a pointer passed by value)。C語言是自由形式語言,即其原始碼的縮排並不影響程式的功能,而是使用分號作為語句的結尾,花括號來表示代碼塊

由於C語言的語言規模較小,若干高層的機制需要使用定義的函式來提供。比如,C語言並沒有直接處理複合對象(例如字串、集合、列表、陣列等)的操作,也沒有對於記憶體分配工具和主記憶體回收工具的直接定義,同時也本身不具有輸入和輸出以及檔案訪問的方法。然而,使用者定義的函式和C語言標準庫中的函式為這些高層的機制提供了可能性。[6]

C語言也具有以下的特性:[6]

  • 基本資料類型包括字元、整型和浮點數等。另外也有衍生的各種資料類型,如指標陣列、結構和聯合。
  • 部份的變數類型可以轉換,例如整數型和字元型變數。
  • 透過指標(pointer),C語言可以容易的對記憶體進行低階控制。
  • 不同的變數類型可以用結構體(struct)組合在一起。
  • 具有基本的控制流:語句組、條件判斷、多路選擇、迴圈等。
  • 函式可以返回各種資料類型的值,並且都可以遞迴呼叫。每次呼叫函式會重新建立變數。
  • C語言只有32個保留字(reserved keywords),使變數、函式命名有更多彈性。
  • 編譯預處理(preprocessor)讓C語言的編譯更具有彈性。

歷史

20世紀70年代,肯·湯姆森為了使其設計的Unix系統更加高效,使用B語言的變種(即C語言)在DEC PDP-7電腦上重寫了Unix。C語言中許多重要概念來源於BCPL語言,其對C語言的影響也間接地來源於B語言。在1978年,丹尼斯·里奇布萊恩·柯林漢合作出版了《C程式設計語言》第一版,事實上即為K&R C標準[7]。1983年,為了制定一個獨立於具體機器且無歧義的C語言標準,美國國家標準協會成立了一個委員會,並在1988年完成了該標準的制定,即ANSI C。此標準同時被國際標準化組織所採納,也被稱作ISO C。

其後,C語言至今經歷了幾次標準更新,誕生了C99C11和目前最新的標準C18。C語言標準的下一次更新C2x目前正在起草中。

語法

C語言的語法相對簡潔而直接。C語言的形式文法國際標準化組織所制定。[8]簡單來說,C語言包括如下文法:

  1. 作為一種指令式編程語言,C語言使用語句執行操作。最常見的語句是運算式語句,由一個運算式後加一個分號組成,可以令系統呼叫函式和為變數賦值;
  2. 註釋: C語言支援單行注釋(以//開頭)和多行注釋(以/*開始,以*/結束);
  3. 資料類型: 基本的資料類型包括整數(int)、浮點數(floatdouble)、字元(char)、枚舉enum等;
  4. 陣列: 陣列是一組相同類型的資料元素的集合。使用以下方法初始化一個五個元素的整數數組:
    int numbers[5] = {1, 2, 3, 4, 5};
    
  5. 封裝結構:結構(struct)、聯合(union);
  6. 結構化編程和控制結構: C語言套件括條件語句(ifelse)、迴圈語句(forwhiledo-while)等;
  7. 跳轉語句:C語言允許使用跳轉關鍵字gotobreakcontinue來實現程式塊之間的跳轉,這和組合語言的jmp關鍵字有一定相似處;
  8. 函式: C語言中的函式是程式的基本模組,可以自訂函式並在程式中呼叫;
  9. 靈活且靠近底層的主記憶體控制機制:C程式設計師可以自由選擇分配何種主記憶體,以及分配多大的主記憶體,如如下代碼所示:
    int *array = (int *)malloc(5 * sizeof(int)); // 分配一個包含五個整數的數組
    free(array); // 釋放使用malloc分配的內存
    

Hello World 程式

 
Brian Kernighan於1978年親筆書寫的「Hello World」程式
"對於所有語言的初學者來說,編寫的第一個程式幾乎都是相同的,即『請列印出下列內容 hello, world』"
– 《C程式設計語言》[6]

現在廣泛被編程初學者使用的"hello, world"程式實例最初就是出現在《C程式設計語言》第一版中。下面是一個在標準輸出裝置(stdout)上列印出 "Hello, world!" 字串的簡單程式。類似的程式,通常作為初學程式語言時的第一個程式:

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

其中只有int,void,return為C語言的關鍵字,前置處理器會將#include <stdio.h>替換為stdio.h檔案的內容。

main函式是C語言程式的入口點

"Hello, world!\n"中的\n是一個跳脫字元,形式為\加上一個字元。所起的作用在ASCII碼中規定。

printf是聲明於stdio.h的函式,關於printf的更多細節,參見printf

關于格式化字串的更多資訊,參見格式化字串

主記憶體管理

C語言的特色之一是:程式設計師必須親自處理主記憶體的分配細節。語言不負責主記憶體邊界檢查,這是因為在執行時進行主記憶體邊界檢查會造成效能問題,與UNIX哲學不符。此特性容易導致緩衝區溢位問題。然而,部分編譯器(如英特爾編譯器)會出於安全性的考量,提供方法以進行執行時主記憶體邊界檢查[9]

大多數C語言實現使用棧(Stack)來儲存函式返回位址/堆疊框基址、完成函式的參數傳遞和函式局部變數的儲存。然而,在部分極特殊的平台上,使用棧並不能獲得最大效率。此時的實現由編譯器決定[10]。 如果程式需要在執行的過程中動態分配主記憶體,可以利用(Heap)來實現。

基本上C程式的元素儲存在主記憶體的時候有3種分配策略:

  • 靜態分配

如果一個變數聲明為全域變數或者是函式的靜態變數,這個變數的儲存將使用靜態分配方式。靜態分配的主記憶體一般會被編譯器放在資料段代碼段來儲存,具體取決於實現。這樣做的前提是,在編譯時就必須確定變數的大小。 以IA32的x86平台及gcc編譯器為例,全域及靜態變數放在資料段的低階;全域及靜態常數放在代碼段的高端。

  • 自動分配

函式的自動局部變數應該隨著函式的返回會自動釋放(失效),這個要求在一般的體系中都是利用棧(Stack)來滿足的。相比於靜態分配,這時候,就不必絕對要求這個變數在編譯時就必須確定變數的大小,執行時才決定也不遲,但是C89仍然要求在編譯時就要確定,而C99放鬆了這個限制。但無論是C89還是C99,都不允許一個已經分配的自動變數執行時改變大小。

所以說C函式永遠不應該返回一個局部變數的位址

要指出的是,自動分配也屬於動態分配,甚至可以用alloca函式來像分配堆(Heap)一樣進行分配,而且釋放是自動的。

  • 動態分配

還有一種更加特殊的情況,變數的大小在執行時有可能改變,或者雖然單個變數大小不變,變數的數目卻有很大彈性,不能靜態分配或者自動分配,這時候可以使用堆(Heap)來滿足要求。ANSI C定義的堆操作函式是malloc、calloc、realloc和free。

使用堆(Heap)主記憶體將帶來額外的開銷和風險。

C語言的標準文件要求了一個平台移植C語言的時候至少要實現的一些功能和封裝的集合,稱為「標準庫」,標準庫的聲明頭部通過前置處理器命令#include進行參照。

在C89標準中:

檔案 簡介說明
<assert.h> 斷言相關
<ctype.h> 字元類型判斷
<errno.h> 標準報錯機制
<float.h> 浮點運算
<limits.h> 各種體系結構限制
<locale.h> 在地化介面
<math.h> 數學函式
<setjmp.h> 跨函式跳轉
<signal.h> 訊號(類似UNIX訊號定義,但是差很遠)
<stdarg.h> 可變參處理
<stddef.h> 一些標準巨集定義
<stdio.h> 標準I/O庫
<stdlib.h> 標準工具庫函式
<string.h> ASCII字串及任意主記憶體處理常式
<time.h> 時間相關

在94年的修正版中

  • <iso646.h>
  • <wchar.h>
  • <wctype.h>

在C99中增加了六個函式庫

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>

以上是C語言的標準。各個系統各自又對C庫函式進行的各種擴充,就浩如煙海了。如POSIX CGNU C等。

工具軟體

工具軟體可以幫助程式設計者避免一些程式中潛藏或容易出現的問題,例如常會造成程式未預期動作或是執行期錯誤的程式碼。

許多語言都有自動原始碼檢查及審計工具,C語言也有類似工具,像是Lint。可以在程式剛寫好時用Lint找出可能有問題的程式,通過Lint後再用C編譯器進行編譯,許多編譯器也可以設定是否要針對一些可能有問題的程式碼提出警告。MISRA C是一套針對嵌入式系統的法則,可主要也是避免一些可能有問題的程式碼。

也有一些編譯器、程式庫或作業系統可以處理一些非標準C語言的功能,例如邊界值檢查、緩衝區溢位偵測、序列化自動垃圾回收功能。

使用像ValgrindIBM Rational Purify英語Purify等軟體工具,或者連結有特別malloc函式的程式庫,有助於找出一些運行期記憶體使用的問題。

經典錯誤

void main() 的用法並不是任何標準制定的[11][12]。 C語言標準語法是 int main(),任何實現都必須支援int main(void) { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[13]。 在 C++ 標準中,main的標準型態應是int,否則類型是由實現定義的。任何實現都必須支援int main() { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[14]

參見

註腳

註解

參考資料

  1. ^ History of C. 2020年12月13日 [2020年10月24日] (英語). 
  2. ^ Programming languages — C (PDF). 2020年12月11日 [2020年12月17日] (英語). 
  3. ^ Ritchie, Dennis M. The Development of the C Language. January 1993 [2008-01-01]. (原始內容存檔於2015-02-03). The scheme of type composition adopted by C owes considerable debt to Algol 68, although it did not, perhaps, emerge in a form that Algol's adherents would approve of. 
  4. ^ TIOBE Programming Community Index [TIOBE編程社群指數]. 2012 [2012-11-03]. (原始內容存檔於2018-12-25) (英語). 
  5. ^ Brian W. Kernighan and Dennis M. Ritchie. The C programming Language. Prentice-Hall. 1988. ISBN 0-13-110362-8 (英語). In C, all function arguments are passed ``by value. 
  6. ^ 6.0 6.1 6.2 Dennis M. Ritchie,Brian W. Kernighan. C程序设计语言. 北京: 機械工業出版社. 2004年1月 [2020-06-10]. ISBN 9787111128069 (中文). 
  7. ^ Stephen Prata. C Primer Plus(第5版). 北京: 人民郵電出版社. 2005年2月: 3-4 [2020-07-15]. ISBN 9787115130228 (中文). 
  8. ^ ISO/IEC JTC1/SC22/WG14 - C. [2022-04-02]. (原始內容存檔於2018-02-12) (英語). 
  9. ^ check-pointers, Qcheck-pointers. Intel. [2021-06-01]. (原始內容存檔於2021-02-15) (英語). 
  10. ^ ISO/IEC 9899:2018 (PDF). [2020-06-10]. (原始內容存檔 (PDF)於2020-07-22). 
  11. ^ Can I write "void main()"?頁面存檔備份,存於網際網路檔案館)The definition void main() { /* ... */ } is not and never has been C++, nor has it even been C.
  12. ^ 用 C99 进行开放源代码的开发. [2011-01-21]. (原始內容存檔於2011-08-12). 
  13. ^ 「The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters: int main(void) { /* ... */ } or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ } or equivalent; or in some other implementation-defined manner.」,引自ISO/IEC 9899:1999, Section 5.1.2.2.1 Program startup
  14. ^ 「An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main: int main() { /* ... */ } and int main(int argc, char* argv[]) { /* ... */ }.」,引自 ISO/IEC 14882, 第一版(1998)、第二版(2003)與第三版(2011), section 3.6.1 Main function

參考資料

外部連結