學習目標
- 了解 C 語言的歷史背景與設計哲學,知道 C 為何至今仍是系統程式設計的核心語言。
- 理解完整的編譯流程:前置處理(preprocessing)→ 編譯(compilation)→ 組譯(assembly)→ 連結(linking)。
- 能逐行解釋經典的 hello world 程式,理解
#include、main、printf各自的角色。 - 學會使用編譯器選項
-Wall -Wextra -std=c11,養成「零警告」的開發習慣。 - 能在 macOS / Linux 終端機獨立完成「編寫 → 編譯 → 執行」的完整循環。
對照 K&R 章節
- K&R《The C Programming Language, 2nd Edition》§1.1 Getting Started
- 補充:附錄 A(參考手冊)中關於翻譯階段(translation phases)的描述
觀念講解
1. C 語言簡史與設計哲學
C 語言由 Dennis Ritchie 於 1972 年前後在貝爾實驗室(Bell Labs)開發,最初的目的是重寫 UNIX 作業系統。 它的前身是 Ken Thompson 的 B 語言,而 B 又源自 BCPL。1978 年 Kernighan 與 Ritchie 出版了第一版 《The C Programming Language》(俗稱 K&R),1988 年的第二版對應 ANSI C(C89)標準,也就是本教程的主軸。
C 的設計哲學可以濃縮為幾句話:
- 信任程式設計師:語言不會擋你做危險的事,效率優先。
- 貼近硬體:C 的型別與運算幾乎直接對應到機器指令,因此常被稱為「可攜的組合語言」。
- 小而精的語言核心:語言本身很小,功能(如輸入輸出)由「函式庫」提供。
之後的標準演進:C89/C90 → C99 → C11(本教程使用)→ C17 → C23。
2. 第一支程式:hello world 逐行解析
#include <stdio.h> /* 引入標準輸入輸出函式庫的宣告 */
int main(void) /* 程式進入點:作業系統從 main 開始執行 */
{
printf("hello, world\n"); /* 呼叫函式庫函式 printf 印出字串 */
return 0; /* 回傳 0 給作業系統,代表正常結束 */
}
逐行說明:
| 程式碼 | 說明 |
|---|---|
#include <stdio.h> |
前置處理指令。把標頭檔 stdio.h 的內容「貼」進來,讓編譯器知道 printf 的原型(prototype)。 |
int main(void) |
每支 C 程式都必須有一個 main 函式。int 表示它回傳整數給作業系統;void 表示不接受參數。 |
{ ... } |
大括號圍出函式本體(block),內含一連串敘述(statements)。 |
printf("hello, world\n"); |
printf 是「格式化列印」函式;\n 是跳脫序列,代表換行字元。敘述以分號 ; 結尾。 |
return 0; |
將結束狀態(exit status)回傳給作業系統:0 代表成功,非零代表錯誤。 |
注意:main 的正確寫法是 int main(void) 或 int main(int argc, char *argv[])。
初學者常見的 void main() 並不是標準寫法,請避免。
3. 編譯流程四階段
當你執行 cc hello.c 時,背後其實經歷了四個階段:
| 階段 | 工具 | 輸入 → 輸出 | 做的事 |
|---|---|---|---|
| 1. 前置處理(Preprocessing) | 前置處理器 cpp | .c → 展開後的原始碼 |
處理 #include、#define、#ifdef,移除註解 |
| 2. 編譯(Compilation) | 編譯器本體 | 原始碼 → 組合語言 .s |
語法/語意分析、最佳化、產生組合語言 |
| 3. 組譯(Assembly) | 組譯器 as | .s → 目的檔 .o |
把組合語言翻成機器碼(object file) |
| 4. 連結(Linking) | 連結器 ld | .o + 函式庫 → 可執行檔 |
把 printf 等函式庫程式碼與你的程式接在一起 |
你可以用選項觀察各階段的中間產物:
cc -E hello.c -o hello.i # 只做前置處理(看看 stdio.h 展開後有多大!)
cc -S hello.c -o hello.s # 產生組合語言
cc -c hello.c -o hello.o # 產生目的檔(不連結)
cc hello.o -o hello # 連結成可執行檔
4. 編譯器選項:養成零警告的習慣
本教程所有程式一律使用以下指令編譯:
cc -std=c11 -Wall -Wextra -o 程式名 原始碼.c
| 選項 | 意義 |
|---|---|
-std=c11 |
指定使用 C11 標準,避免不同編譯器的預設標準不一致 |
-Wall |
開啟「常見且幾乎都是 bug」的警告(名字叫 all 其實不是全部) |
-Wextra |
再開啟一批額外警告,例如「未使用的參數」 |
-o 名稱 |
指定輸出檔名;不加的話預設輸出 a.out |
警告不是錯誤,但幾乎每個警告都在指出潛在的 bug。 本教程要求所有範例 「零錯誤、零警告」通過編譯,請把這當成基本紀律。
在 macOS 上,cc 實際上是 Apple clang;在多數 Linux 上,cc 是 gcc。
兩者對本教程的程式行為一致。
5. 執行程式與結束狀態
./hello # 執行目前目錄下的 hello
echo $? # 印出上一個程式的結束狀態(return 0 → 印出 0)
./ 表示「目前目錄」,因為安全考量,shell 預設不會在目前目錄找執行檔。
main 的 return 0 就是 echo $? 看到的數字,這是程式與作業系統溝通成敗的管道。
範例程式
| 檔案 | 內容 | 編譯與執行 |
|---|---|---|
01_hello.c |
經典 hello world 與變化版:多行輸出、跳脫序列示範 | cc -std=c11 -Wall -Wextra -o 01_hello 01_hello.c && ./01_hello |
02_compile_stages.c |
用程式本身示範前置處理(#define、__FILE__、__LINE__ 等預定義巨集)與編譯期資訊 |
cc -std=c11 -Wall -Wextra -o 02_compile_stages 02_compile_stages.c && ./02_compile_stages |
03_exit_status.c |
示範 return 與結束狀態、main 的正確簽名、簡單的算術輸出 |
cc -std=c11 -Wall -Wextra -o 03_exit_status 03_exit_status.c && ./03_exit_status; echo $? |
常見錯誤與陷阱
陷阱 1:忘記分號
printf("hello, world\n") /* 錯誤:少了分號 */
return 0;
編譯器會報 expected ';'。注意:錯誤訊息指到的行號常常是「下一行」,
因為編譯器讀到下一行才發現分號不見了。修正:每個敘述以 ; 結尾。
陷阱 2:void main() 或省略回傳型別
void main() /* 不標準:main 必須回傳 int */
main() /* C89 允許省略回傳型別,C11 不允許 */
修正:一律寫 int main(void)。這不只是風格問題——作業系統確實會讀取
main 的回傳值作為程式的結束狀態。
陷阱 3:字串跨行直接換行
printf("hello,
world\n"); /* 錯誤:字串常值不能直接跨行 */
修正方式有兩種:
printf("hello,\nworld\n"); /* 用 \n 表達換行 */
printf("hello, "
"world\n"); /* 相鄰字串常值會自動串接 */
陷阱 4:把警告當耳邊風
int main(void)
{
int x; /* -Wall 會警告:x 未初始化就使用 */
printf("%d\n", x);
return 0;
}
未初始化變數的值是垃圾值(indeterminate),每次執行可能不同。 看到警告請立刻修正,而不是等程式行為異常才回頭找。
練習題
- 基本題:修改 hello world,讓它印出三行文字:你的名字、就讀學校/公司、一句座右銘。每行結尾都要換行。
- 跳脫序列:寫一支程式,只用一個
printf,印出包含雙引號與反斜線的句子:She said: "C:\ is a path."(提示:\"與\\)。 - 觀察編譯階段:對你的 hello world 依序執行
cc -E、cc -S、cc -c,觀察.i、.s、.o檔案。回答:hello.i大約有幾行?為什麼比原始碼大這麼多? - 結束狀態:寫一支程式
return 42;,執行後用echo $?驗證。再試試return 256;,觀察並解釋看到的數字(提示:結束狀態只有 8 位元)。 - 故意製造錯誤:分別移除
#include <stdio.h>、刪掉一個大括號、拼錯printf為pritnf,各編譯一次,記下每種錯誤訊息。學會「讀懂錯誤訊息」是除錯的第一步。 - 挑戰題:查詢
man 3 printf(或man printf),找出%s與%d的意義,寫一支程式印出My favorite number is 7.,其中7必須用%d帶入。
延伸閱讀
- K&R 2/e,§1.1;Introduction(了解本書寫作背景)
- Dennis Ritchie,〈The Development of the C Language〉(HOPL II, 1993)—— C 語言起源的第一手資料
man cc、man clang:查閱編譯器選項完整清單- GCC/Clang 文件中
-Wall實際包含的警告清單——理解每個警告在防什麼 - 下一單元預告:變數與基本型別,程式終於要開始「記住」資料了