上一篇確立:數組名在「表達式中」退化為指標。但聲明語境下有三種情況:
extern int a[]; → 仍是數組類型int a[10]; → 分配空間,數組類型void func(int a[]); → 編譯器強制轉為指標 void func(int *a);
int a[]、int a[10]、int *a 完全等價,函數內部拿到的都是指標。效率考量。 C 採用「值傳遞」。若數組不退化,每次調用需將整個數組內容複製到棧幀,對大數組極其昂貴。退化為指標後,僅傳遞首地址(4/8 字節),函數直接操作原記憶體。
這也是 C++ 引入「引用傳遞」
void func(int (&a)[10])的原因之一:保留數組類型資訊(長度),同時避免拷貝。
a[i] 統一重寫為 *(a + i)。for(i=0;i<10;i++) a[i]=0;1. 基址 a → R1 (循環外不變) 2. 變數 i 地址 → R2 (循環外不變) 3. 循環體: 3.1 [R2] → R3 (取 i 的值) 3.2 R3 * scale → R4 (計算偏移) 3.3 R1 + R4 → R5 (目標地址) 3.4 0 → [R5] (存值)
優勢:基址、i 地址均為編譯期常量,易優化、易預取。
p=a; for(i=0;i<10;i++) *p++=0;1. 元素大小 → R5 (循環外) 2. p 地址 → R0 (循環外) 3. 循環體: 3.1 [R0] → R1 (取 p 當前值 = 目標地址) 3.2 0 → [R1] (存值) 3.3 R1 + R5 → R1 (指標後移) 3.4 R1 → [R0] (更新 p)
差異:指標遍歷需額外「讀取指標變數」與「寫回指標變數」,多兩次記憶體訪問(除非寄存器優化)。
| 場景 | 數組寫法 | 指標寫法 | 是否等價 |
|---|---|---|---|
表達式中訪問 a[i] | ✓ | ✓ (指標亦可用下標) | 是,均重寫為 *(a+i) |
| 函數參數聲明 | void f(int a[]) | void f(int *a) | 是,唯一合法互換處 |
| 函數調用傳實參 | 傳數組名 | 傳指標 | 是,數組名自動退化為指標 |
外部聲明 extern | extern int a[]; | extern int *a; | 否! 型別不匹配,鏈接/運行錯誤 |
| 定義 | int a[10]; | int *a; | 否! 前者分配 10 個 int,後者僅分配 1 個指標 |
int a[100];,聲明 必須 extern int a[]; 或 extern int a[100];,絕不可 extern int *a;。sizeof(a) 返回指標大小,而非數組大小。需額外傳長度參數。void f(int a[][COLS]) 第二維必須固定,退化為 int (*a)[COLS](指向陣列的指標),而非 int **。徹底理解「編譯期地址常量」與「運行期地址變數」的二元本質,配合標準規定的三條退化規則,即可在任何語境下精準判斷數組與指標的行為,寫出零驚喜的 C 代碼。