const 關鍵字深度解析:從語法到實戰最佳實務const 是 C 語言中用於宣告「唯讀物件」的類型修飾詞。正確使用可提升程式碼健壯性、啟用編譯器最佳化、並明確表達設計意圖。本文系統解析 const 與指標的組合規則、常見誤區、與 #define/enum 的差異,並提供實戰建議。
記住一條「虛線分隔法」:在宣告中沿著 * 劃一條虛線,const 出現在虛線哪一邊,就修飾哪一邊的實體。
const int *p; // 虛線左側:*p 是 const → 指向 const int 的指標 (p 可變,*p 不可變)
int const *p; // 同上,const 在型別名稱右側效果相同
int * const p; // 虛線右側:p 是 const → const 指標指向 int (p 不可變,*p 可變)
const int * const p; // 兩邊皆 const → const 指標指向 const int
const int n = 5;
int arr[n]; // ❌ C89/C90 編譯錯誤:陣列大小需為常數運算式
原因:const int n 在記憶體中仍佔用儲存空間,屬「執行期唯讀變數」,非「編譯期常數」。
解法:
#define N 5 或 enum { N = 5 };typedef char *pStr;
char str[] = "hello";
const pStr p = str; // ❌ p 是 const 指標 (char * const),非指向 const char
p++; // 編譯錯誤:p 自身不可修改
分析:const pStr 經 typedef 展開為 char * const,而非 const char *。建議:避免對指標型別使用 typedef,或改用 typedef const char *cStr; 明確語意。
char *p = "literal"; // ⚠️ 僅為相容舊碼,實際型別應為 const char *
p[0] = 'L'; // 未定義行為:嘗試寫入唯讀記憶體區段
正確寫法:const char *p = "literal"; 或 char p[] = "literal"; (陣列於堆疊可修改)。
char a[3] = "abc"; // ⚠️ 合法但危險:無 '\0' 結尾
printf("%s", a); // 未定義行為:printf 會越界讀取
建議:char a[] = "abc"; 讓編譯器自動計算長度 (含 null)。
| 宣告 | 語意 | 適用場景 |
|---|---|---|
void func(const int *p) | 函數不修改 *p | 輸入參數、只讀資料 |
void func(int *const p) | 函數不修改 p 本身 | 輸出緩衝區基址固定 |
const char *get_msg(void) | 回傳指向唯讀字串 | 字串常值、靜態緩衝區 |
char *const get_buf(void) | 回傳固定緩衝區指標 | 單例緩衝區、硬體暫存器映射 |
const volatile int *port = (const volatile int *)0xFFFF0000;
// 唯讀 (程式不寫入) + 易變 (硬體可隨時改變值)
const 全域變數預設 internal linkage (需 extern 導出);C 則為 external linkage。const int n = 5; int a[n]; (常數運算式);C89/90 不允許,C99 VLA 允許但非標準 C++。constexpr (C++11) 提供真正編譯期常數,C 尚無對應關鍵字 (可用 _Static_assert 配合宏模擬)。const**。const char ***。typedef,若必要請在文件說明 const 位置。enum 或 #define,確保編譯期可見。const 協助編譯器最佳化 (如將唯讀資料置於 .rodata 段)。const 是否正確表達設計意圖。結語:const 不只是語法糖,更是「契約式設計」在 C 語言的體現。掌握虛線分隔法、理解編譯期常數與執行期唯讀的差異、避開 typedef 陷阱,即能寫出更安全、可維護的 C 程式碼。