C 語言參數傳遞本質是值傳遞。理解一級/二級指針能否修改外部變量,關鍵在於「值」是什麼。通過 GCC -m32 -O0 生成的 AT&T 汇編,我們一窺究竟。
gcc -m32 -O0 test.c -o test # 32位,關閉優化,便於對照源碼
objdump -d test > test.asm
gdb ./test
void increase(int value) { value = value + 1; }
int main() { int count = 7; increase(count); printf("%d", count); } // 輸出 7
sub $0x20,%esp # 分配棧幀
movl $0x7,0x1c(%esp) # count = 7 存放在 [esp+0x1c]
mov 0x1c(%esp),%eax # 載入 count 值到 EAX
mov %eax,(%esp) # **複製值** 到棧頂作為參數
call increase
addl $0x1,0x8(%ebp) # 操作的是**副本** (位於調用者棧頂)
結論:函數內修改的是副本,外部 count 不變。
void increase(int* ptr) { *ptr = *ptr + 1; }
int main() { int count = 7; increase(&count); printf("%d", count); } // 輸出 8
lea 0x1c(%esp),%eax # 取 count **地址** 到 EAX
mov %eax,(%esp) # 壓入地址作為參數
call increase
mov 0x8(%ebp),%eax # 取出參數 (地址)
mov (%eax),%eax # 解引用:取地址處的值
lea 0x1(%eax),%edx # +1
mov 0x8(%ebp),%eax # 再取地址
mov %edx,(%eax) # 寫回:修改原 count
結論:傳遞的是地址值 (也是值傳遞),但通過地址可修改目標內存。
void increase(int* ptr) { *ptr = *ptr + 1; ptr = NULL; }
int main() { int count = 7; int* countPtr = &count; increase(countPtr); printf("%p", countPtr); } // countPtr 不為 NULL
原因:countPtr 的地址值被複製給形參 ptr,函數內 ptr = NULL 只修改副本。
& 取地址,類型加一級 *。* 或 ->,類型減一級 *。關係鏈:
int count ──&──► int* countPtr ──&──► int** pp
│ │ │
▼ ▼ ▼
值 地址 指針的地址
void increase(int** ptr) { **ptr = **ptr + 1; *ptr = NULL; }
int main() {
int count = 7;
int* countPtr = &count;
increase(&countPtr); // 傳遞一級指針的地址
// count = 8, countPtr = NULL
}
# main 調用前
lea 0x1c(%esp),%eax # 取 countPtr 的地址 (即 &countPtr)
mov %eax,(%esp) # 壓棧
# increase 內部
mov 0x8(%ebp),%eax # 取出 &countPtr
mov (%eax),%edx # *ptr → countPtr 的值 (即 &count)
mov (%edx),%ecx # **ptr → count 的值
# ... 運算 ...
mov %eax,(%edx) # *ptr = NULL → 修改 countPtr 本身
| 參數類型 | 傳遞內容 | 能修改外部值 | 能修改外部指針 | 典型場景 |
|---|---|---|---|---|
int | 值副本 | 否 | 否 | 只讀輸入 |
int* | 地址副本 | 是 (*ptr) | 否 | 輸出參數、修改結構體字段 |
int** | 指針地址副本 | 是 (**ptr) | 是 (*ptr) | 修改指針本身 (鏈表頭插入、重新分配內存) |
void push(Node** head, int val) {
Node* newNode = malloc(sizeof(Node));
newNode->val = val;
newNode->next = *head;
*head = newNode; // 修改外部 head 指針
}
void func(int** p) 調用時必須傳 &ptr,而非 ptr。if (!ptr || !*ptr) return;。*ptr = NULL (修改指針) 和 **ptr = 0 (修改值)。二級指針本質是「指針的指針」,通過雙重解引用觸達外部指針變量。從汇編視角看,一切皆是地址值的傳遞與內存讀寫。掌握這一點,指針難題迎刃而解。