C 語言的基本概念

    前言

    我們在本文中說明 C 語言的基本概念。

    C 程式碼所用的副檔名

    C 程式碼使用兩種副檔名,原始碼 (source) 使用 .c 為副檔名,標頭檔 (header) 使用 .h 為副檔名。原始碼中會放入實作 (implementation),標頭檔則是擺放宣告 (declaration)。

    原始碼和標頭檔的檔案名稱沒有特別限制,只要是作業系統中合法的檔名即可。一般建議用簡短、有意義的檔名,並用底線取代空白。

    一開始練習的程式規模很小,將所有的程式碼寫在原始碼中無妨。待程式規模變大後,會將原始碼拆分到多個檔案中,這時候就會用到標頭檔。

    第一個 C 程式

    最小的 C 程式如下:

    int main() {}
    

    基本上,這個程式什麼事都沒做。但這個程式太乏味了,我們把這個程式稍微加點東西。以下是加上註解的 Hello World 程式:

    /* Include the library for standard input and output. */
    #include <stdio.h>
    
    /* Entry to the main program. */
    int main(void)
    {
        /* Print some string to the console. */
        printf("Hello World\n");
    
        /* Exit the program successfully. */
        return 0;
    }
    

    同樣地,這個 Hello World 程式也是簡單到不行,但可由此觀察到 C 程式的架構。

    編譯 C 程式的步驟

    表面上,編譯 C 程式只是一行指令或一個 IDE 的按鈕就解決的事。但編譯 C 程式實際上分為四個步驟:

    • 前處理 (Preprocessing)
    • 編譯 (Compilation)
    • 組譯 (Assembly)
    • 連結 (Linking)

    前處理將含巨集 (macro) 的 C 程式碼轉換成沒有巨集的 C 程式碼。嚴格上來說,前處理不算編譯的一部分,只是經由一連串字串代換的動作改寫原始碼而已。我們會在後文介紹 C 巨集。

    編譯 (compilation) 是整個編譯 (building) 的前半段。在這個階段,C 編譯器會將 C 原始碼轉換成等效的組合語言原始碼。由此可知,C 仍然是高階語言,只是寫起來不若現代語言那麼方便。

    組譯會將組合語言原始碼轉換成機械碼。轉換後的檔案為目的檔 (object files)。目的檔只是編譯的中間產物,無法使用。

    最後,透過連結,將目的檔轉為執行檔 (executable)。執行檔即為可使用的電腦程式 (program)。Unix 或類 Unix 系統的執行檔不加副檔名,Windows 系統的執行檔的副檔名為 .exe

    剛開始練習的程式很簡短,只會用到標準函式庫的函式,所以編譯的步驟較簡單。隨著程式的規模上升,會使用模組化的方式將程式碼分散在多個檔案中,也有可能會用到外部函式庫,這時候編譯的步驟就會變複雜。

    大小寫敏感性 (Case Sensitivity)

    C 語言的程式碼會區分大小寫。所以,fooFooFOO 視為不同的識別字。在實務上,大部分的識別字會用小寫字母。全大寫字母僅用於常數 (constant) 和列舉 (enumeration)。按照 C 社群的慣例,幾乎不用 CapitalCase 來寫識別字。

    即使 C 語言會區分大小寫,我們也不應濫用這項特性,以免寫出難以維護的程式碼。

    空白 (Spaces)、縮進 (Indentations)、排版 (Code Formatting)

    C 語言對於空白、縮進等版面安排相當自由,並沒有 Python 那種強制縮進的規則。甚至還有故意寫出難以閱讀的 C 程式碼的比賽,像是 IOCCC (The International Obfuscated C Code Contest)。但我們仍然鼓勵讀者在寫 C 程式碼時排版程式碼,或是使用程式碼自動重排軟體。因為整齊的程式碼對於日後維護會有所幫助。

    由於 C 程式碼在編譯後就會轉成機械碼,對 C 程式碼刻意做縮小化 (minification) 只會影響到編譯程式碼的時間,對程式執行速度沒有幫助。所以這是沒有意義的行為。

    註解 (Comments)

    程式碼中的註解不會執行,所以可寫入一般文字。C 語言有兩種風格的註解。ANSI C 是使用一對 /**/ 把註解文字包起來,像是以下範例:

    /* Some comment. */
    

    由於傳統的註解能夠跨越多行,故未完全淘汰。

    C99 後,引入單行註解。單行註解以 // 開頭,之後同一行內的所有文字皆視為註解。像是以下範例:

    // Some single-line comment.
    

    單行註解比較易寫,但無法跨越多行,所以未取代傳統的註解。

    除了這兩種正統的註解外,還有另一種利用巨集來註解掉整段程式碼的手法:

    #if 0
        printf("It won't compile\n");
    #endif
    

    這樣操作的原理在於巨集會在編譯前就執行,相關程式碼會被抹去,等同於這段程式碼不存在。

    註解的用途是記錄程式人撰寫程式碼時的意圖或想法。對於教學用的程式,註解可放入教學用文字。

    有時候我們會用註解暫時隱藏掉一段程式碼,防止該段程式碼編譯及運作。例如,我們在除錯時,利用註解遮蔽掉可能有問題的程式碼,然後逐步縮小註解的範圍,直到找到錯誤為止。

    引入函式庫

    C 語言的語法刻意保持精簡,大部分的功能由函式庫來實現。甚至標準輸出入這種基本的功能也是藉由函式庫來實現。所以,學 C 語法不會花太久時間,但要寫到熟練則需要反覆練習。

    引入函式庫的語法是 #include。該語法是一種巨集 (macro),但我們一開始不需要在意巨集的寫法,因為 #include 敘述的寫法是固定的。

    引入函式庫時,有兩種寫法。一種是以一對角括號 <> 包住函式庫名稱。一種則是用一對分號 "" 包住函式庫名稱。C 語言沒有強制要使用那一種方式。

    一般來說,使用標準函式庫或外部函式庫時,使用角括號:

    #include <stdlib.h>
    

    使用內部函式庫時,則使用分號:

    #include "mylib.h"
    

    當使用分號引用函式庫時,C 編譯器會優先從 C 程式碼所在的路徑找標頭檔,故可用來引用內部函式庫。

    主函式 (Main Function)

    主函式是 C 程式的起始點,一般的 C 語言皆有主函式。但在嵌入式系統或是作業系統本身的 C 程式碼中,主函式則不是必需的。

    標準 C 的主函式名稱固定為 main。Windows C 則有不同的主函式名稱。但 Windows C 有很多非標準 C 的特性,只有要寫 Windows C 時再去學習即可。

    如果該 C 程式不需要接收命令列參數,則使用以下方式來寫:

    int main(void)
    {
        /* Implement your code here. */
    }
    

    若該 C 程式需要接收命令列參數,則改用以下方式來寫:

    int main(int argc, char *argv[])
    {
        /* Implement your code here. */
    }
    

    除了命令列參數外,如果想要接收環境變數,可以用以下方式改寫:

    int main(int argc, char *argv[], char *env[])
    {
        /* Implement your code here. */
    }
    

    現在這種寫法比較少見,因為標準 C 可用 getenv() 函式取得環境變數,沒有必要再用這種寫法。

    至於以下寫法是錯誤的:

    void main(void)
    {
        /* DON'T USE THIS IN PRODUCTION CODE. */
    }
    

    雖然 Visual C++ 可編譯通過,但這種寫法並非標準 C 的一部分,故不建議使用。

    表達式 (Expression) 和敘述 (Statement)

    表達式會回傳值,像是 "Hello World\n" 是表達式,該表達式是 "Hello World" 字串再附帶一個換行符號。而敘述代表單一指令,像是 printf("Hello World\n"); 是一條敘述。C 語言的單行敘述會用 ; 結尾。

    C 程式由一條條敘述組成,預設情形下,程式會由上往下依序執行敘述。但我們有許多改變程式行進流程的方式,像是控制結構或函式呼叫等。

    離開狀態 (Exit Status)

    我們在程式結束時回傳 0

    return 0;
    

    這代表程式正常結束。

    C 程式在結束時,會向系統回傳一個整數,用來代表程式的狀態。一般來說,回傳 0 代表程式正常結束,回傳非零值 1 代表程式異常結束。

    有些程式會用不同的回傳值表示不同的異常狀態,但除了回傳 0 表示正常結束以及回傳 1 表示異常結束外,目前 C 語言對其他的回傳值沒有共識。也就是說,用回傳值來判斷程式狀態不是很牢靠,我們也不鼓勵讀者使用複雜的回傳值來表示程式的離開狀態。

    C 執行期函式庫

    雖然 C 程式表面上不需要虛擬機器,但其實 C 程式仍然有執行期函式庫。像是 C 程式具有主函式,且主函式會回傳離開狀態,C 程式需要執行期函式庫來處理主函式。

    C 語言的執行期函式庫很小,通常是以組合語言來撰寫,會在編譯 C 程式碼時一併加入 C 程式中,所以不需要另外安裝虛擬機器。C 語言的運行期函式庫稱為 crt0。

    MSVC (Visual C++) 在不同版本的 C 編譯器使用不同的執行階段函式庫,所以在 Windows 上安裝以 C 撰寫的應用程式時,有時會需要安裝特定版本的執行階段函式庫。在 MSVC 中的 C 執行階段函式庫稱為 Visual C++ 可轉散發套件。

    C 專案

    C 語言本身沒有專案的概念,現今所見的 C 專案是利用社群開發工具來管理 C 程式碼的方式。剛開始學 C 語言時,建議直接用 IDE 管理 C 程式碼。等學一段時間後,再開始學習用開發工具建立 C 專案。筆者在這裡這裡有更多說明,歡迎有需要的讀者前往閱讀。

    電子書籍

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

    現代 C 語言程式設計

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