C 語言 `const` 與指針組合產生的類型系統是許多開發者的痛點。本文從單級指針語法入手,層層遞進至雙重指針陷阱,用一個經典反例證明:允許 `char**` 隱式轉換為 `const char**` 將導致 `const` 保護機制失效,進而修改唯讀記憶體引發未定義行為。理解此機制,是寫出正確 `const-correct` 代碼的關鍵。
| 聲明 | 含義 | 口訣 |
const char *p / char const *p | 指向 `const char` 的指針,**指向的內容不可修改**,指針可變 | const 在 * 左 → 鎖定內容 |
char * const p | 指向 `char` 的 `const` 指針,**指針本身不可修改**,指向的內容可修改 | const 在 * 右 → 鎖定指針 |
const char * const p | 指向 `const char` 的 `const` 指針,**雙重不可修改** | 雙 const |
二、單級指針隱式轉換規則
規則:`T*` 可隱式轉換為 `const T*`,**反之不可**。
char *p = "hello"; // 警告:字串常量應賦給 const char*
const char *cp = p; // ✅ 安全:增加限制
// char *p2 = cp; // ❌ 編譯錯誤:試圖移除限制
理由:若允許 `const char* → char*`,即可通過 `p2` 修改 `cp` 指向的唯讀內容,違背 `const` 契約。
三、雙重指針陷阱:`char**` → `const char**` 為何被禁止
看似合理的假設
既然 `char* → const char*` 安全,那麼 `char** → const char**` 應該也安全?**錯誤!**
反例證明:允許轉換將導致 `const` 保護失效
const char c = 'x'; // 唯讀對象,存儲在 .rodata
char *p1; // 普通指針
const char **p2 = &p1; // 假設允許:char** 隱式轉 const char**
*p2 = &c; // 1. 合法:const char* 賦值給 const char* (p2 解引用類型)
*p1 = 'y'; // 2. 合法:char* 賦值 (p1 類型為 char*)
// 現在:p1 指向 c,通過 p1 修改了 const 對象 c → 未定義行為!
步驟拆解
- `p2` 指向 `p1` (類型視為 `const char*`)。
- `*p2 = &c` 將 `p1` 修改為指向 `c` (合法,目標類型 `const char*` 匹配)。
- `*p1 = 'y'` 通過 `p1` (類型 `char*`) 修改 `c` — **編譯器允許,運行時崩潰/靜默破壞**。
結論:`char**` 指向的記憶體單元類型為 `char*`,允許寫入 `char*` 值。若視為 `const char**`,則該單元類型視為 `const char*`,但實際單元仍可寫入 `char*` (如 `&c`),導致類型系統邏輯崩潰。
四、編譯器行為:警告而非錯誤
const char **p2 = &p1; // GCC/Clang/MSVC: warning: assignment from incompatible pointer type
歷史包袱:早期 C 標準 (C89) 未嚴格規定此行為,編譯器妥協發出警告而非錯誤。現代編譯器在嚴格模式 (`-Werror=incompatible-pointer-types`) 可升級為錯誤。
五、正確的 `const` 多級指針轉換規則
標準規則 (C99 6.5.16.1):多級指針轉換要求**每一級**都增加 `const` 限定,且**不允許跳過級別**。
| 來源類型 | 目標類型 | 合法性 | 說明 |
| `char**` | `const char**` | ❌ 非法 | 第二級增加 const,但第一級未增加 |
| `char**` | `char* const*` | ✅ 合法 | 第一級增加 const (指針本身) |
| `char**` | `const char* const*` | ✅ 合法 | 兩級均增加 const |
| `const char**` | `const char* const*` | ✅ 合法 | 第一級增加 const |
六、實戰最佳實踐
- 函數參數:接受只讀字串陣列時,聲明為 `const char* const*` (指針與內容雙重只讀) 或 `const char**` (僅內容只讀,指針陣列可修改)。
- 避免多級指針:優先用結構體封裝、傳遞長度+單級指針、或 C++ `std::vector`。
- 必要時顯式轉型:明知安全 (如僅讀取不修改指針陣列) 可用 `
(const char**)` 強制轉型,但需加註釋說明安全理由。
- 啟用嚴格警告:`-Wall -Wextra -Wconversion -Werror=incompatible-pointer-types` (GCC/Clang),`/W4 /we4047` (MSVC)。
七、C++ `const_cast` 與 `constexpr` 視角
- C++ `const_cast(p)` 同樣危險,**僅用於與無 `const` 舊 API 互操作**,且必須確保運行時不修改。
- C++17 `std::string_view`、`std::span` 可優雅替代多級指針參數,編譯期保證只讀語義。
「`char**` 不能賦值給 `const char**`」不是編譯器刁難,而是類型系統為防止 `const` 保護被繞過而設立的必要防火牆。內化「逐級增加 const」規則,能讓你在指針迷宮中保持類型安全的清醒。
- 上一篇文章: C 語言實現泛型棧:void 指針、記憶體複製與 qsort 風格介面設計
- 下一篇文章: Windows C 語言遠程 DLL 注入完整實現:OpenProcess、VirtualAllocEx、CreateRemoteThread 核心鏈路
Copyright ©
程式碼之家 All Rights Reserved