C 語言指針宣告中 `const` 的位置決定了「誰是常量」。搞混這三者是無數初學者(甚至資深開發者)的痛點。本文用「右左法則」、記憶體圖解、代碼實測、編譯器報錯示例,徹底釐清:
從變數名向外閱讀,遇到 `const` 修飾誰,誰就不可變:
| 宣告 | 讀法 (從變數名 p 向右再向左) | p 值可變? | *p 值可變? | 典型用途 |
|---|---|---|---|---|
const int *pint const *p | p 是一個指針 → 指向 const int → 指向的 int 不可變 | ✅ 是 | ❌ 否 | 函數參數傳入只讀緩衝區:void print(const char *str); |
int * const p | p 是一個 const 指針 → 指向 int → 指針本身不可變 | ❌ 否 | ✅ 是 | 固定指向特定硬體暫存器/記憶體映射 IO:volatile uint32_t * const REG = (void*)0x40000000; |
const int * const p | p 是一個 const 指針 → 指向 const int → 雙重不可變 | ❌ 否 | ❌ 否 | 只讀查找表、常量字串:const char * const msg = "Hello"; |
int a = 10, b = 20;
const int *p = &a; // p 指向 a
*p = 30; // ❌ 編譯錯誤: assignment of read-only location '*p'
p = &b; // ✅ 合法: 改變指向
printf("%d", *p); // 20
記憶體圖:
p (可變指標) ──┐
▼
+--------+
| 10 | ← 唯讀區/棧區 (a)
+--------+ ↑
| 20 | ← 唯讀區/棧區 (b) p 可指向這裡
+--------+
int a = 10, b = 20;
int * const p = &a; // 初始化後不可改變指向
*p = 30; // ✅ 合法: 修改指向的值
p = &b; // ❌ 編譯錯誤: assignment of read-only variable 'p'
printf("%d", a); // 30
記憶體圖:
p (唯讀指標) ──┐
▼
+--------+
| 30 | ← 可讀寫區 (a) 內容可改
+--------+
| 20 | ← 另一變數 (b) p 永遠無法指向這裡
+--------+
int a = 10;
const int * const p = &a; // 綁定終身
*p = 20; // ❌ 錯誤
p = NULL; // ❌ 錯誤
// 唯一合法操作: 讀取 *p
// 二級指針
int val = 100;
int *p1 = &val;
int * const p2 = p1; // p2 是指針常量,指向 int
const int *p3 = p1; // p3 是常量指針,指向 const int
const int * const *p4 = &p3; // p4 指向「常量指針」,p4 可變,*p4 (即 p3) 不可變,**p4 不可變
// 函數參數傳遞規則 (重要!)
void f1(int *p); // 可接受 int*, 不可接受 const int* (丟棄 const)
void f2(const int *p); // 可接受 int* 和 const int* (加上 const)
void f3(int * const p); // 形參 p 為指針常量,僅影響函數內部 p 是否可改指向
void f4(const int * const p); // 形參雙重 const
for (Node * const cur = head; cur; cur = cur->next) 防止誤寫 `cur = something_else`。volatile uint32_t * const UART_DR = (void*)0x4000C000; — `volatile` 防優化,`const` 鎖定位址。const char * const GREETING = "Hello"; 雙重保障。const_cast` (C++) 或強制轉型 (C) 移除 `const` 導致 UB,除非確知原物件非常量。const int *p; // 指向 const int 的指針 → 保護 *p
int * const p; // 指向 int 的 const 指針 → 保護 p
const int * const p; // 指向 const int 的 const 指針 → 保護 p 和 *p
// 記憶口訣:const 修飾右邊的類型 (若右邊無類型則修飾左邊的 *)
// const int *p → const 修飾 int → *p 是 const
// int * const p → const 修飾 *p → p 是 const
// const int * const p → 兩個 const 分別修飾 int 和 *p
徹底搞懂這三者,不僅能避開編譯錯誤,更能在 API 設計中精確表達「所有權與可變性契約」,寫出更安全、自文檔化的 C 代碼。