許多 C 語言學習者混淆 `char *`(字符指針)與 `char[]`(字符數組)。本文從內存模型、指針算術、字符串處理函數約定三個維度,徹底釐清二者的本質區別與聯繫。
棧內存布局:
地址: 0x7ffe... 0x7ffe...+1 0x7ffe...+2 0x7ffe...+3 0x7ffe...+4 0x7ffe...+5
內容: 'h' 'e' 'l' 'l' 'o' '\0'
str → 首元素地址(常量,不可修改)
sizeof(str) = 6
數組在棧上分配 6 字節,內容可修改(`str[0] = 'H'` 合法)。
棧內存: 只讀數據段(.rodata):
p (8字節) ──────→ 'h' 'e' 'l' 'l' 'o' '\0'
sizeof(p) = 8 (64位系統)
指針 `p` 在棧上,指向只讀段的字符串字面量。**嘗試修改 `p[0] = 'H'` 導致未定義行為(通常崩潰)。**
指針指向堆內存,內容可修改,用完需 `free(p)`。
int arr[5] = {1,2,3,4,5};
int *pi = arr;
pi++; // pi 增加 sizeof(int) 字節,指向下一個 int
char str[] = "hello";
char *pc = str;
pc++; // pc 增加 sizeof(char) = 1 字節,指向下一個字符
指針類型決定了指針算術的步長(`sizeof(類型)`),這是類型系統賦予指針的語義。
C 標準庫函數(`strlen`、`strcpy`、`printf("%s")` 等)的規約是:
參數 `char *s` 必須指向一個以 '\0' 結尾的字符序列。
函數內部只是從起始地址逐字節遍歷,直到遇到 '\0'。因此:
| 場景 | 正確做法 | 錯誤示例 |
|---|---|---|
| 需要修改字符串內容 | `char str[] = "hello";` 或 `malloc` + `strcpy` | `char *p = "hello"; p[0]='H';` |
| 函數參數接收字符串 | `void func(const char *s)` | `void func(char *s)` 並嘗試修改 |
| 返回動態分配字符串 | 文檔註明「調用者負責 free」 | 返回棧上數組地址 |
| 字符串字面量 | 賦值給 `const char *` | 賦值給 `char *`(C++ 編譯器會報錯,C 僅警告) |
char[] 是「有內容的容器」,char * 是「指向內容的標籤」。字符串不是類型,而是「以 '\0' 結尾」的數據協議。理解內存佈局、指針算術、庫函數約定三位一體,才能在 C 語言字符串處理中游刃有餘。