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

C 語言 const 修飾指標完全指南:四種組合的語義差異與陷阱

     編輯:關於C語言

const 與指標的兩個維度

指標涉及兩個可修改性:

  1. 指標指向的內容 (Pointee) 能否通過指標修改?
  2. 指標本身 (Pointer) 能否指向別處?

const 的位置決定鎖定哪一維度:

  • const 在 * 左側 (或類型右側) → 鎖定 內容
  • const 在 * 右側 → 鎖定 指標本身

四大經典組合實測

1. 普通指標 int* p

int i = 1, j = 100;
int* p = &i;
*p = 2;     // ✅ 修改內容
p = &j;     // ✅ 改變指向

2. 指向常量的指標 const int* p / int const* p


int i = 1, j = 100;
const int* p = &i; // 或 int const* p = &i;  兩者完全等價

*p = 2;     // ❌ 編譯錯誤:assignment of read-only location
p = &j;     // ✅ 指標本身可變
i = 2;      // ✅ 原變量直接修改合法 (const 只限制通過 p 訪問)
printf("%d", *p); // 輸出 2
核心語義:「我承諾不通過這個指標修改數據」,適用於只讀參數、字符串字面量、查找表。

3. 常量指標 (指標本身不可變) int* const p


int i = 1, j = 100;
int* const p = &i; // 必須初始化,之後不可改指向

*p = 2;     // ✅ 可修改內容
p = &j;     // ❌ 編譯錯誤:assignment of read-only variable 'p'
核心語義:「這個指標終身綁定這個地址」,適用於硬件寄存器映射、固定內存池起始地址、嵌入式外設基址。

4. 雙重常量 const int* const p


int i = 1, j = 100;
const int* const p = &i; // 內容不可改、指向不可改

*p = 2;     // ❌ 錯誤
p = &j;     // ❌ 錯誤
i = 2;      // ✅ 原變量直接修改合法
核心語義:「完全只讀視圖」,適用於常量表導出、不可變配置結構體。

語法記憶口訣:「右定身,左定值」

  • 看 * 右邊 有無 const → 決定指標本身能否變 (身)
  • 看 * 左邊 (含類型修飾符) 有無 const → 決定指向內容能否變 (值)

const int * p;     // * 左有 const → 值鎖定;* 右無 const → 身自由
int const * p;     // 同上 (const 修飾 int)
int * const p;     // * 右有 const → 身鎖定;* 左無 const → 值自由
const int * const p; // 兩邊都有 → 雙鎖定

函數參數傳遞中的隱式轉換規則

實參類型形參類型合法性說明
int*const int*✅ 隱式轉換「可寫」→「只讀」安全收窄
const int*int*❌ 需強制轉型「只讀」→「可寫」危險擴大,編譯器禁止
int* constint*✅ 值傳遞指標副本,const 屬性不傳遞
const int* constconst int*✅ 隱式轉換雙鎖定 → 單鎖定 (值)

實戰陷阱案例

陷阱 1:二級指標的 const 傳遞


void Foo(const int** pp) { /* ... */ }

int main()
{
    int* p = NULL;
    Foo(&p); // ❌ 警告/錯誤:int** 不能隱式轉為 const int**
    // 正確:const int* const* pp 或 const int* p; Foo(&p); (C++允許,C標準嚴格禁止)
}
原因:允許 int** → const int** 會破壞 const 承諾 (可通過修改中間指標指向 const 數據,再通過原 int* 修改)。

陷阱 2:結構體成員的 const 保護


typedef struct { const int* data; size_t len; } Buffer;

void Process(Buffer* buf)
{
    buf->data[0] = 1; // ❌ 錯誤:data 指向 const int
    buf->data = NULL; // ✅ 合法:指標本身非 const
}

陷阱 3:字符串字面量的類型


char* s = "hello";  // ❌ C++非法,C警告:字面量類型是 const char[]
const char* s = "hello"; // ✅ 正確
s[0] = 'H';         // ❌ 運行時崩潰 (只讀段)

最佳實踐清單

  1. 默認給指標參數加 const:除非函數確需修改指向內容,聲明 void Func(const int* p)。
  2. 指標不需重新指向時加 const:int* const p = &x; 讓編譯器幫你防禦誤賦值。
  3. API 邊界嚴格區分:庫接口返回 const T* 表示只讀視圖,返回 T* 表示調用方擁有所有權 (需 free)。
  4. 宏定義常量表:static const int* const TABLE[] = { ... }; 雙重保護。

總結

搞清 const int*、int const*、int* const、const int* const 的語義邊界,是 C 程序員從「能跑通」進階到「寫得穩」的分水嶺。記住 「右定身,左定值」,配合編譯器警告 -Wwrite-strings -Wcast-qual,讓 const 成為你代碼中最忠實的靜態分析器。

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