「陣列與指標的區別」是 C 語言學習者永恆的困惑。課本常說「陣列名是指標」,實際上這是一個危險的過度簡化。本文從內存佈局、編譯期符號解析、運行期訪問機制三個維度,深度剖析兩者在訪問方式上的根本差異,並釐清 extern char a[] 與 extern char *p 的聲明陷阱。
extern)。理解這一點是看懂後續「為何 extern char a[] 與 extern char a[100] 等價,但 extern char *p 截然不同」的前提。
x = y;
編譯器為每個變數分配地址(左值),生成指令從該地址讀取值(右值)放入寄存器參與運算。
對陣列 a[i]:
a 的基地址(符號 a 即代表該地址)。i 轉換為位元組偏移 i * sizeof(element)。
因此 extern char a[] 和 extern char a[100] 都只是告訴編譯器:「a 是一個陣列,其元素從地址 a 開始」。大小資訊對「計算偏移」無影響,故兩者等價。
對指標 p[i](或 *(p+i)):
p 本身是一個變數,儲存著目標地址。p 的內容(即目標地址)。
若聲明 extern char *p,編譯器只知道 p 是一個指標量,必須生成「讀取 p 的值 → 作為地址 → 讀取目標」的雙重間接指令。若錯誤地將陣列聲明為指標(extern char *a),編譯器會把陣列的首個元素內容當作地址,導致災難性後果。
| 特性 | 陣列 char a[] | 指標 char *p |
|---|---|---|
符號 a/p 代表 | 陣列首元素地址(編譯期常量) | 指標變數本身的地址(儲存目標地址的容器) |
| 內存分配 | 定義時分配元素空間 | 定義時僅分配指標自身空間(通常 4/8 字節) |
| sizeof 結果 | 整個陣列大小(字節數) | 指標大小(4 或 8 字節) |
| 可修改性 | 陣列名不可修改(非左值) | 指標變數可修改(可指向別處) |
| 訪問指令 | 直接「基址+偏移」 | 「讀指標值→基址+偏移」雙重間接 |
| 初始化字串字面值 | char a[] = "abc"; 複製到陣列,可修改 | char *p = "abc"; 指向唯讀區,修改行為未定義 |
函數參數表中,char a[]、char a[10]、char *a 完全等價,都會被編譯器調整為 char *a(指標)。這是 C 語言為了效率設計的「退化」規則:陣列作為參數傳遞時,實際傳遞的是首元素地址。因此函數內無法用 sizeof(a) 獲得陣列長度,必須額外傳遞長度參數。
陣列與指標在「訪問機制」上本質不同:前者是編譯期靜態地址計算,後者是運行期動態間接引用。混淆兩者會導致聲明不匹配、內存訪問越界、sizeof 誤用等難以排查的錯誤。牢記「陣列名不是指標,只是在大多數表達式中『退化』為首元素地址」,並理解 extern 聲明時的語義差異,是寫出正確、高效 C 代碼的關鍵。
下一篇將探討函數調用中的陣列/指標差異、兩者的深層聯系,以及何時它們真正「等價」。