指標是 C 語言的靈魂,也是最大的門檻。網路上流傳「指標就是地址」、「指標是整數」、「指標是無符號整數」等說法,皆不精確。本文從編譯器、記憶體模型、類型系統三個層面,重新定義指標的真實面貌。
int *p = NULL;
int a = 20;
unsigned int b = 30;
p = a; // 編譯錯誤:不能從 int 轉換為 int*
p = b; // 編譯錯誤:不能從 unsigned int 轉換為 int*
p = p * 5; // 編譯錯誤:指標不支援乘法
若指標只是整數/無符號整數,上述操作應合法。事實上,**指標是獨立的類型類別**。
int a = 10;
int b;
b = (int)&a; // 強制轉為整數:合法 (但丟失類型資訊)
*b += 20; // 錯誤:int 不能間接存取
// 必須轉回指標類型才能解引用
*((int*)b) += 20; // 正確:(int*) 恢復了類型資訊
**結論**:指標 = **記憶體地址 + 類型資訊**。類型決定了解引用時的位元寬度、指標運算步長、別名規則。
| 屬性 | 說明 | 影響 |
|---|---|---|
| 指向類型 (Pointee Type) | `int*` 指向 `int`,`char*` 指向 `char` | 解引用大小、`sizeof(*p)`、指標算術步長 (`p+1` 前進 `sizeof(T)` 位元組) |
| 儲存值 (Address Value) | 記憶體位址數值 | 實際指向的位置,可為 NULL (0) |
int arr[100];
printf("%p %p\n", (void*)arr, (void*)&arr); // 相同位址
printf("%p %p\n", (void*)(arr+1), (void*)(&arr+1));
// arr+1 : 位址 + sizeof(int) = +4
// &arr+1 : 位址 + sizeof(int[100]) = +400
// 類型決定步長!
int *p; // 指向 int 的指標
int *p[10]; // 指標陣列:10 個 int*
int (*p)[10]; // 陣列指標:指向包含 10 個 int 的陣列
int *func(); // 回傳 int* 的函式
int (*func)(); // 函式指標:指向回傳 int 的函式
int **pp; // 雙重指標:指向 int* 的指標
void *vp; // 通用指標:未知類型,不可解引用/算術,需轉型
const int *p; // 指向常數的指標:不可透過 p 修改值
int *const p; // 常數指標:p 本身不可修改指向
const int *const p; // 皆不可修改
// 1. 定義函式指標類型
typedef unsigned long (*OPERATE_FUNC)(char *msg, int len);
// 2. 宣告陣列
static OPERATE_FUNC op_table[10] = {0};
// 3. 註冊
op_table[0] = HandleLogin;
op_table[1] = HandleLogout;
// ...
// 4. 調用
unsigned long result = op_table[cmd_id](buffer, length);
| 錯誤 | 後果 | 正確做法 |
|---|---|---|
| `int *p; *p = 10;` | 野指標,未定義行為 | 初始化:`int *p = &var;` 或 `malloc` |
| `free(p); *p = 20;` | Use-after-free | `free` 後設 `p = NULL` |
| `char *s = "hello"; s[0] = 'H';` | 修改字串常數 (唯讀段) | `char s[] = "hello";` 或 `malloc` 複製 |
| `void f(int a[10]) { sizeof(a); }` | 退化為指標,得 4/8 | 傳遞長度參數或用 `int (*a)[10]` |
| 指標轉換違反對齊 | 總線錯誤 (SIGBUS) | 確保位址滿足目標類型對齊要求 |
指標不是地址,不是整數,而是 **「帶有類型資訊的記憶體視圖」**。類型決定了編譯器如何解讀該記憶體區域:讀寫多少位元組、算術前進多少、能否與其他指標別名。深刻理解「地址 + 類型 = 指標」,才能寫出正確、高效、可維護的 C 代碼。下次看到 `int *p`,請在腦中呈現:一個 4/8 位元組的儲存單元,裡面存著一個位址,編譯器承諾該位址處有 4 位元組的有效 `int` 物件。