數組名的真實身份:指針常量而非指針變量
繼上篇建立內存與指針基礎模型後,本文聚焦 一維數組 與指針的深度綁定關係。這是 C 語言最核心、也是最易誤解的知識點之一——數組名在絕大多數表達式中退化為指向首元素的指針常量。
一、標量與聚合類型的區別
int a; // 標量:單一值
int b[10]; // 聚合類型:10個 int 的連續集合
b[0] ~ b[9] 分別訪問各元素。關鍵問題:b (數組名) 的類型與值是什麼?
二、核心結論:數組名 = 首元素地址常量
在 C 語言中,數組名在表達式中(除 sizeof、&、字符串字面值初始化外)永遠退化為指向首元素的指針常量,即 &b[0]。其類型為 int * (指向 int 的指針)。
「指針常量」vs「指針變量」關鍵差異
| 特性 | 指針變量 int *p | 指針常量 int b[10] (數組名) |
| 值可修改 | 是 p = &x; | 否 b = &x; 編譯錯誤 |
| 內存佈局 | 存儲地址的變量單元 | 無獨立存儲單元,編譯期符號直接替換為首地址常數 |
| sizeof 結果 | 指針大小 (4/8B) | 整個數組大小 (10 * sizeof(int)) |
| & 操作結果 | 指針的地址 int** | 數組首地址 int(*)[10] (指向整個數組的指針) |
經典錯誤示範
int a[10] = {0};
int num = 99;
a = # // ❌ 編譯錯誤:不能給常量賦值
// 正確做法:用指針變量中轉
int *p = a; // p 指向 a[0]
p = # // 合法
三、下標引用 [ ] 的指針本質
C 標準定義:array[subscript] 等價於 *(array + subscript)。這不是近似,是語言規定的等價轉換規則。
推導過程
b[3]
→ *(b + 3) // 定義等價
→ *(&b[0] + 3) // b 退化為 &b[0]
→ *(地址 + 3 * sizeof(int)) // 指針算術:按元素類型大小步進
→ *(&b[3]) // 到達第4個元素地址
→ b[3] 的值 // 解引用獲取值
實戰等價轉換表 (假設 int arr[10]; int *ap = arr + 2;)
| 指針表達式 | 等價數組表達式 | 說明 |
ap | &arr[2] / arr + 2 | 指向第3個元素地址 |
*ap | arr[2] | 第3個元素的值 |
ap[0] | *(ap+0) = *ap | 下標0即首元素 |
ap + 6 | &arr[8] / arr + 8 | 指向第9個元素地址 |
*ap + 6 | arr[2] + 6 | 優先級:* 高於 +,先解引用再加6 |
ap[6] | *(ap+6) = arr[8] | 下標轉指針算術 |
&ap | &ap | 指針變量自身的地址 (int**) |
四、震驚的合法寫法:2[ap] = ap[2]
2[ap]
→ *(2 + ap) // 定義展開
→ *(ap + 2) // 加法交換律
→ ap[2] // 合法且等價!
這揭示了下標運算符的本質:a[b] 只是 *(a+b) 的語法糖,左右操作數可互換(前提是一個為指針、一個為整數)。這也是 C 語言「靈活到危險」的經典例證。
五、效率迷思:指針 vs 下標
「假定兩種寫法都正確,下標絕不會比指針更高效,但指針有時會比下標更高效。」
現代編譯器優化極其強大,簡單循環下兩者機器碼往往完全一致。指針優勢場景:
- 複雜指針算術難以用下標表達時(如多級指針鏈表遍歷)
- 手動優化關鍵熱點路徑,減少索引計算指令(極少數嵌入式場景)
- 代碼語義表達:指針語義強調「遍歷/鏈接」,下標強調「隨機訪問/索引」
建議:優先選擇語義清晰、不易越界的寫法,交給編譯器優化。
六、總結:數組與指針的「似是而非」
- 數組名是常量,不可修改指向;指針變量可修改指向。
- 數組有長度信息 (
sizeof(arr)),指針丟失長度(退化為裸指針)。
- 函數參數傳遞:
void f(int arr[]) 實際調整為 void f(int *arr),長度信息丟失,需額外傳長度參數。
- 下標 = 指針算術 + 解引用,二者在機器層面同構。
下一篇將挑戰 多維數組,揭秘「行主序」存儲下的指針遞歸結構與 int (*p)[N] 類型聲明的讀法。
- 上一篇文章: C語言指針深度解析(一):內存、地址與指針基本概念
- 下一篇文章: C語言指針深度解析(三):多維數組內存佈局與指針類型完全指南
Copyright ©
程式碼之家 All Rights Reserved