K&R 以精煉著稱,重讀時仍發現許多「以為懂卻不夠透徹」的細節。本文整理前幾章核心知識點,補充現代編譯器行為與最佳實踐,作為查閱手冊。
C89 要求區塊開頭宣告;C99 允許混合宣告與程式碼。現代風格傾向「就近宣告、就近初始化」,縮短變數生命週期,利於編譯器最佳化與閱讀維護。
externextern;定義在後 → 需 extern 宣告。file1.c 定義 int g_val;,file2.c 需 extern int g_val;(通常置於共用標頭)。extern int foo(void); 可省略 extern,但建議保留以明確語意。/* 舊式 K&R 風格:無參數資訊 */ int old_style(); /* 新式 ANSI C 風格:完整原型 */ int new_style(void); /* 無參數 */ int with_args(int a, double); /* 參數名可省略 */
差異:舊式呼叫時編譯器不檢查參數個數/型別,極易產生靜默 Bug;新式啟用完整檢查。現代鐵律:永遠使用新式原型,並啟用 -Wstrict-prototypes -Wmissing-prototypes。
若呼叫未見宣告的函式,C89 隱式假設 int func();(傳回 int、參數不定)。回傳非 int 或參數不匹配時,行為未定義。解決:-Werror=implicit-function-declaration 升級為編譯錯誤。
int 寬度的決定鏈int=32、long=32、pointer=64;Linux x64 採 LP64 long=64、pointer=64)int 寬度結論:int 非固定 32 位元,嵌入式開發務必使用 <stdint.h> 固定寬度型別(int32_t、uint16_t 等)。
| 型別 | 總位元 | 符號 | 指數 | 尾數 | 偏移量 | 有效十進制位數 |
|---|---|---|---|---|---|---|
| float | 32 | 1 | 8 | 23 | 127 | 6~7 |
| double | 64 | 1 | 11 | 52 | 1023 | 15~16 |
驗證範例:3.0f → 0x40400000(符號 0、指數 128、尾數 0.5)。聯合體 union { float f; uint32_t u; } 可安全檢視位元模式,避免嚴格別名規則違規。
char 有號性可移植性char 預設有號性由實作定義(ARM 多為 unsigned,x86 多為 signed)。最佳實踐:
char;signed char / unsigned char;getchar() 回傳值必須用 int,以容納 EOF(-1)。int 的整數型別(char、short、位欄)參與運算前提升為 int 或 unsigned int。-1 < 1u 為真(-1 轉為巨大無號數)。float 不自動轉 double(C89 要求轉換,C99 移除此要求,現代編譯器多保留 float 運算以節省頻寬)。123 /* int */ 123U /* unsigned int */ 123L /* long */ 123ULL /* unsigned long long */ 0x1F /* 十六進位 */ 037 /* 八進位 */ 0b00011111 /* 二進位(C23 / GNU 延伸) */ 1.0f /* float */ 1.0 /* double */ 1.0L /* long double */
| 特性 | enum | #define |
|---|---|---|
| 除錯器可見性 | 保留符號名 | 前處理階段消失 |
| 型別安全 | 獨立型別(C23 強化) | 無 |
| 自動編號 | 支援 | 手動維護 |
| 位址可取 | 否(右值) | 否 |
現代建議:優先 enum / constexpr(C++) / static const,僅在條件編譯需求時用宏。
static 限制作用域。register 僅建議,編譯器決定;不可取址。volatile 防止編譯器最佳化掉「看似多餘」的讀寫,適用硬體暫存器、訊號處理共享變數。int → char:截斷高位(模 256 映射)。char → int:有號擴展或零擴展,視 char 有號性而定。(type)expr:建立臨時變數轉換後再參與運算。printf 格式字串不執行轉換,%f 對應 double,傳 float 會因預設引數提升為 double 而正常;但 %d 傳 long long 會導致未定義行為。printf("%d %d", ++n, power(2,n)) 兩參數誰先算不保證 → 拆分語句。a[i] = i++ 未定義行為(C11 明確禁止)。z = (n>0) ? f : n → z 型別為 float(第二、三操作數經慣用轉換統一)。(a=3, a*4) 值為 12,常用於 for 多變數迭代。char *s = "lit" → 字串常值,唯讀區(.rodata),不可修改。char s[] = "lit" → 字元陣列,堆疊/資料區,可修改。strlen 不計入結束 \0;sizeof(s) 含結束符。K&R 雖成書於 1988,其核心觀念(宣告即契約、型別即契約、未定義行為即地雷)至今仍是 C/C++ 工程師必修內功。建議配合現代標準(C11/C17/C23)與編譯器警告旗標同步研讀。