數組不是指標,指標不是數組。 它們在大多數表達式上下文中表現相似 (退化為指向首元素的指標),但本質完全不同。
// file1.c 定義
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
// file2.c 聲明 —— 錯誤!
extern int *arr; // 編譯通過,鏈接通過,運行崩潰/亂碼
// file2.c 正確聲明
extern int arr[]; // 或 extern int arr[10];
原因:
int arr[10] 定義:分配 40 字節連續內存,符號 arr 綁定該地址,類型含長度信息。extern int *arr 聲明:編譯器認為 arr 是指標變量 (4/8 字節),去該地址取值 作為指標,實際讀到的是 1 (首元素值) 當地址 → 非法訪問。| 特性 | 數組 int a[10] | 指標 int *p |
|---|---|---|
| 內存佈局 | 連續 10 個 int (40 字節) | 1 個指標變量 (4/8 字節),指向某處 int |
sizeof | 40 (整個數組大小) | 4 或 8 (指標大小) |
&a 類型 | int (*)[10] (指向數組的指標) | int ** (指向指標的指標) |
| 地址值 | &a == &a[0] (數值相等,類型不同) | &p != p (指標變量地址 vs 指向的地址) |
| 可修改性 | 不可修改 (非常量左值,a = p 編譯錯誤) | 可修改 (p = a、p++ 合法) |
| 初始化 | int a[] = {1,2,3} (編譯器計算長度) | int *p = a (指向現有數組) |
a[i] vs p[i] 底層步驟
// 數組訪問 a[i] (假設 a 地址 0x1000)
1. 計算偏移:0x1000 + i * sizeof(int)
2. 直接讀取該地址內容
// 指標訪問 p[i] (假設 p 地址 0x2000, p 值 0x1000)
1. 讀取 p 變量值:0x1000
2. 計算偏移:0x1000 + i * sizeof(int)
3. 讀取該地址內容
指標多一次間接讀取 (取指標值)。現代 CPU 快取下差異微乎其微,但概念上必須清晰。
char *p = "hello"; // "hello" 在常量區,p 指向它,p 可修改指向,*p 不可修改內容
char a[] = "hello"; // "hello" 複製到棧上數組 a,a 內容可修改,a 不可修改指向
a[0] = 'H'; // OK
p[0] = 'H'; // UB:修改常量區
C 標準規定:函數參數聲明為數組,一律調整為指向首元素的指標。
// 以下三種聲明完全等價
void func(int a[]);
void func(int a[10]); // 10 被忽略,僅作文檔
void func(int *a); // 本質
// 調用時
int arr[10];
func(arr); // 實際傳遞 &arr[0] (int*)
後果:
sizeof(a) 返回指標大小,無法獲得數組長度。void func(int *a, size_t n)。void func(int a[][10]) → void func(int (*a)[10]),第二維長度 必須 保留。const 與 static 的特殊情況
// 文件作用域
static int arr[10]; // 內部鏈接,每編譯單元一份
const int arr[10] = {1}; // C 語言外部鏈接 (需 extern), C++ 內部鏈接
// 函數內部
static int arr[10]; // 靜態存儲期,僅初始化一次
const int arr[10] = {1}; // 自動存儲期,每次調用初始化 (C99 VLA 除外)
int arr[10],.h 聲明 extern int arr[] (不寫長度更通用)。void func(int *arr, size_t n),文檔說明 arr 指向至少 n 個元素。void func(const int *arr, size_t n) 防止誤修改。p + i 而非 p++ 在循環中,保持 p 不變便於調試。struct { size_t len; int data[]; } 替代指標+單獨分配,提升緩存局部性。| 場景 | 數組 | 指標 |
|---|---|---|
| 定義 | 分配連續內存 | 分配指標變量 |
| sizeof | 整體大小 | 指標大小 |
| 賦值 | 不可 (非左值) | 可 |
| 參數傳遞 | 退化為指標 | 按值傳遞地址 |
| 字符串字面量 | 複製內容 | 指向常量區 |
| 動態分配 | VLA / malloc + 指標訪問 | 原生支援 |
理解本質,不再被「數組名就是指標」的過度簡化誤導,才是 C 語言進階的關鍵一步。