C 語言「值傳遞」機制導致函數內修改指針變量本身(而非指向的內存)不影響外部,是經典面試題與新手坑。本文通過內存模型圖解、代碼對比、修正方案三維度徹底講透。
void change_prt(int *p); // 聲明
int main() {
int *prt, b[3] = {1, 2, 3};
prt = b; // prt 指向 b[0]
change_prt(prt); // 傳遞 prt 的值(地址)
printf("%d\n", *prt); // 輸出 1,而非預期 3
return 0;
}
void change_prt(int *p) {
p += 2; // 修改局部副本 p,指向 b[2]
// *p = 10; // 這樣才會修改內容
}
調用前 main 棧幀: 調用時 change_prt 棧幀:
+------------------+ +------------------+
| prt (指針變量) | ---> | p (參數,副本) | ---> 同一地址 (b[0])
| 值: &b[0] | | 值: &b[0] |
+------------------+ +------------------+
change_prt 內執行 p += 2:
+------------------+ +------------------+
| prt (指針變量) | | p (參數,副本) | ---> &b[2] (新地址)
| 值: &b[0] 不變 | | 值: &b[2] |
+------------------+ +------------------+
返回後:p 銷毀,prt 仍指向 &b[0]
change_prt(prt) 將 prt 的值(地址)拷貝給參數 pp += 2 只改變局部參數 p 的值,main 中的 prt 毫不知情*p = 10 修改的是 p 指向的內存,該內存被 prt 共享void change_prt(int **pp) { // 接收指針地址
*pp += 2; // 解引用一次,修改原指針值
}
int main() {
int *prt, b[3] = {1, 2, 3};
prt = b;
change_prt(&prt); // 傳遞 prt 的地址
printf("%d\n", *prt); // 輸出 3 ✓
return 0;
}
內存模型:pp 指向 main 棧幀中的 prt,*pp 即 prt 本身。
int* move_prt(int *p, int offset) {
return p + offset;
}
int main() {
int *prt, b[3] = {1, 2, 3};
prt = b;
prt = move_prt(prt, 2); // 接收返回值更新 prt
printf("%d\n", *prt); // 輸出 3 ✓
return 0;
}
| 概念 | 對應關系 |
|---|---|
| 原始文件 F | 數組 b (內存數據) |
| 快捷方式 S1 | 指針 prt (指向 b[0]) |
| 複製快捷方式 S2 | 參數 p (拷貝 prt 的值) |
| 修改 S2 指向 | p += 2 (S2 現在指向別處,S1 不變) |
| 用 S1 打開修改內容 | *prt = 10 (原文件 F 內容變了) |
| 用 S2 打開修改內容 | *p = 10 (同一文件 F 內容變了) |
| 代碼 | 效果 | 是否影響外部 |
|---|---|---|
void f(int *p) { p = q; } | 修改參數指向 | ❌ 否 |
void f(int *p) { *p = 10; } | 修改指向的內容 | ✅ 是 |
void f(int **pp) { *pp = q; } | 修改原指針指向 | ✅ 是 |
void f(int *p) { p += 2; } | 移動參數指向 | ❌ 否 |
** 或返回新指針;需修改內容 → 傳 *void f(const int *p) 承諾不修改內容;void f(int *const p) 承諾不修改指向 (參數為常量指針,但仍為副本)void f(int* &p) 直接綁定原指針,語法清晰無二級指針認知負擔// C++ 引用傳遞指針 (推薦 C++ 項目使用)
void change_prt(int* &p) {
p += 2; // 直接修改外部 prt
}
C 語言只有值傳遞。指針作為參數時,傳遞的是地址值的副本。修改副本(p += 2)不影響原指針;修改副本指向的內存(*p = 10)影響共享內存。理解「指針變量」與「指向的內存」的區別,是掌握 C 內存模型的關鍵一步。