const 承諾「不通過此符號修改值」,但組合指針時極易混淆。本文提供右左讀法、四大組合模式表、編譯期/運行期行為、常見陷阱與修正,一次講透。
const char *p→ p is a pointer to const char (指針常量:指向常量的指針)
char * const p→ p is a const pointer to char (常量指針:指針本身是常量)
const char * const p→ p is a const pointer to const char (雙重常量)
| 聲明 | 口訣 | p 可修改? | *p 可修改? | 典型用途 |
|---|---|---|---|---|
const int *pint const *p | pointer to const int | ✅ | ❌ | 函數參數防修改:void foo(const int *p) |
int * const p | const pointer to int | ❌ | ✅ | 固定緩衝區指針:int * const buf = malloc(N); |
const int * const p | const pointer to const int | ❌ | ❌ | 只讀只指:const int * const p = &global_const; |
int *p | pointer to int | ✅ | ✅ | 完全可變 |
const 修飾的左值賦值、++/--、傳給非 const 參數 → 編譯錯誤。const 變量若存儲在 .rodata (全局/靜態),嘗試通過指針強制修改 → 段錯誤 (SIGSEGV)。棧上 const 可能被修改 (未定義行為)。| 陷阱代碼 | 問題 | 修正 |
|---|---|---|
void f(int *p) { *p = 1; } | 棄用 const 護衛,編譯警告但可能運行期崩潰 | void f(const int *p) 或傳遞副本 |
char *str = "literal"; str[0] = 'L'; | 字符串字面量在 .rodata,不可寫 | char str[] = "literal"; (棧上副本) 或 const char *str |
const int *p = &x; *p = 10; | 直接違反 const 承諾 | 去掉 const 或用非 const 指針 (需確保原對象非真常量) |
int const *p = &x; p = &y; | 以為 p 不可改 | 記住:int const * 只保護 *p,p 本身可改 |
int * const p = &x; p = &y; | 以保護 *p 為由修改 p | int * const 只保護 p,*p 可改 |
// 1. 輸入指針:承諾不修改指向數據
size_t strlen(const char *s); // 標準庫規範
// 2. 輸出指針:不加 const,調用者負責分配
int read_line(char *buf, size_t n);
// 3. 輸入輸出指針:不加 const
int parse_int(char **str, int *out); // *str 推進,*out 寫結果
// 4. 指針本身不變 (罕見)
void use_buffer(int * const buf, size_t n); // buf 不會被重新指向
| 特性 | C const | C++ constexpr | #define |
|---|---|---|---|
| 類型安全 | ✅ | ✅ | ❌ |
| 編譯期常量 | ❌ (非字面量類型) | ✅ | ✅ (文本替換) |
| 可取地址 | ✅ | ✅ | ❌ |
| 作用域 | 塊/文件 | 塊/命名空間 | 文件 (無作用域) |
const 是 C 語言類型系統的契約。牢記 「從右向左讀」,區分 「指針常量」 (保護被指對象) 與 「常量指針」 (保護指針本身)。函數參數盡量加 const 表達意圖,編譯器將幫你攔截違約代碼。