首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

《C 程式設計語言》(K&R)閱讀筆記(一):宣告、記憶體佈局與型別系統

     編輯:關於C語言

前言

K&R 以精煉著稱,重讀時仍發現許多「以為懂卻不夠透徹」的細節。本文整理前幾章核心知識點,補充現代編譯器行為與最佳實踐,作為查閱手冊。

一、宣告

1.1 變數宣告位置

C89 要求區塊開頭宣告;C99 允許混合宣告與程式碼。現代風格傾向「就近宣告、就近初始化」,縮短變數生命週期,利於編譯器最佳化與閱讀維護。

1.2 外部連結與 extern

  • 同一轉譯單元:定義在使用前 → 可省略 extern;定義在後 → 需 extern 宣告。
  • 跨檔案:file1.c 定義 int g_val;,file2.c 需 extern int g_val;(通常置於共用標頭)。
  • 函式同理:extern int foo(void); 可省略 extern,但建議保留以明確語意。

1.3 舊式 vs 新式函式宣告

/* 舊式 K&R 風格:無參數資訊 */
int old_style();

/* 新式 ANSI C 風格:完整原型 */
int new_style(void);          /* 無參數 */
int with_args(int a, double); /* 參數名可省略 */

差異:舊式呼叫時編譯器不檢查參數個數/型別,極易產生靜默 Bug;新式啟用完整檢查。現代鐵律:永遠使用新式原型,並啟用 -Wstrict-prototypes -Wmissing-prototypes。

1.4 隱式宣告陷阱

若呼叫未見宣告的函式,C89 隱式假設 int func();(傳回 int、參數不定)。回傳非 int 或參數不匹配時,行為未定義。解決:-Werror=implicit-function-declaration 升級為編譯錯誤。

二、記憶體佈局與整數寬度

2.1 int 寬度的決定鏈

  1. 處理器字長(如 64 位元)
  2. 作業系統 ABI(如 Windows x64 維持 int=32、long=32、pointer=64;Linux x64 採 LP64 long=64、pointer=64)
  3. 編譯器依 ABI 定義 int 寬度

結論:int 非固定 32 位元,嵌入式開發務必使用 <stdint.h> 固定寬度型別(int32_t、uint16_t 等)。

2.2 IEEE 754 浮點數佈局驗證

型別總位元符號指數尾數偏移量有效十進制位數
float3218231276~7
double6411152102315~16

驗證範例:3.0f → 0x40400000(符號 0、指數 128、尾數 0.5)。聯合體 union { float f; uint32_t u; } 可安全檢視位元模式,避免嚴格別名規則違規。

三、型別系統細節

3.1 char 有號性可移植性

char 預設有號性由實作定義(ARM 多為 unsigned,x86 多為 signed)。最佳實踐:

  • 文字處理用 char;
  • 算術/位運算明確用 signed char / unsigned char;
  • 接收 getchar() 回傳值必須用 int,以容納 EOF(-1)。

3.2 整數提升與慣用算術轉換

  • 位元寬度小於 int 的整數型別(char、short、位欄)參與運算前提升為 int 或 unsigned int。
  • 混合有號/無號運算:若無號型別秩 ≥ 有號型別,有號轉無號;否則無號轉有號。經典坑:-1 < 1u 為真(-1 轉為巨大無號數)。
  • float 不自動轉 double(C89 要求轉換,C99 移除此要求,現代編譯器多保留 float 運算以節省頻寬)。

3.3 常數後綴與進制

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 */

3.4 列舉 vs 宏常數

特性enum#define
除錯器可見性保留符號名前處理階段消失
型別安全獨立型別(C23 強化)無
自動編號支援手動維護
位址可取否(右值)否

現代建議:優先 enum / constexpr(C++) / static const,僅在條件編譯需求時用宏。

3.5 儲存類別與生命週期

  • 自動:區塊內、堆疊、生命週期=區塊執行期、未初始化值不定。
  • 靜態:靜態儲存區、程式全程、預設零初始化、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)與編譯器警告旗標同步研讀。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved