要深入理解 數組、指針和函數參數傳遞 的底層 ABI(Application Binary Interface)實現,需要從以下幾個維度出發進行學習:
一、什么是 ABI?
ABI 是編譯器和操作系統之間的協定,規定了:
- 函數如何調用(參數傳遞、返回值)
- 棧幀布局(局部變量、返回地址、保存寄存器)
- 調用約定(Calling Convention)
- 數據的對齊方式(alignment)
- 如何在內存中布局結構體、數組、類對象等
例如:x86_64 系統通常使用 System V AMD64 ABI
二、函數參數傳遞 ABI 行為:值 / 地址 / 數組
1. 普通變量(按值傳遞)
void foo(int a) {// 棧幀中的 a 是實參副本
}
- 編譯器會將
a
作為 整數寄存器(如rdi
)傳入 - 或在棧中開空間拷貝參數(當寄存器用完或大于特定大小)
2. 指針(傳地址)
void foo(int* p) {*p = 10;
}
- 指針
p
是 8 字節地址,通常放在寄存器(如rdi
) *p
會間接訪問內存(間接尋址)
3. 數組參數(退化為指針)
void foo(int arr[]) {arr[0] = 10;
}
int arr[]
會被 退化為int* arr
- ABI 上是 按指針傳遞
三、C++ 底層函數調用過程(以 x86_64 System V 為例)
調用 foo(int a, int* p)
時的流程(簡化):
main():a = 42;int* p = &a;call foo(a, p)
底層匯編(偽代碼):
mov edi, 42 ; 第一個參數 a → edi
mov rsi, &a ; 第二個參數 p → rsi
call foo
四、函數棧幀結構(以 GCC / x86_64 為例)
一個函數的棧幀通常如下:
┌───────────────┐ ← 高地址│ 返回地址 │ ← 調用者壓入│ old %rbp │ ← 保存上層基址│ 參數備份/局部變量 ││ 臨時寄存器保存 │└───────────────┘ ← %rsp 棧頂
調用過程:
call foo
→ 跳轉并壓入返回地址push %rbp
→ 建立新的基址指針(%rbp)sub rsp, n
→ 為局部變量分配空間
五、數組傳值傳引用的差異 ABI 視角下分析
1. 按值傳遞數組(不推薦)
void foo(int arr[5]) { ... } // 實際變為 int* arr
- 編譯器不會復制整個數組
- 傳的是指針地址
- 在函數內對
arr[i]
的修改會影響外部
2. 真正的數組按值傳遞(必須用 std::array
或結構體包裝)
struct ArrWrap {int arr[5];
};void foo(ArrWrap a);
- 編譯器會 完整拷貝結構體 到寄存器或棧
- 不會影響原數組內容
六、C++ 對象參數 ABI 行為(指針/引用/拷貝)
參數類型 | ABI 表現 |
---|---|
T | 可能按值復制進棧或寄存器 |
T& | 傳指針,指向原對象 |
T* | 明確傳地址 |
const T& | 仍然傳地址,只是禁止修改 |
七、數組和指針的底層行為對比
void foo(int* p); // 編譯器看作地址傳遞
void bar(int arr[10]); // 實質上也是 int* arr
舉例說明區別:
int arr[10];
foo(arr); // arr 退化為 &arr[0]
sizeof(arr)
是 40sizeof(p)
是 8(在 64 位系統)
void size_test(int* p, int arr[10]) {cout << sizeof(p) << endl; // 8cout << sizeof(arr) << endl; // 8(退化后)
}
八、調試驗證:GCC 下使用 objdump
或 gdb
編譯為匯編查看 ABI 細節:
g++ -O0 -S main.cpp -o main.s
或者用 gdb
單步跟蹤:
gdb ./a.out
(gdb) break foo
(gdb) run
(gdb) info registers
九、結構體中的數組傳遞 ABI 規則
struct Data {int x;int arr[4];
};void process(Data d); // 會按值拷貝整個結構體(如果不大)
如果結構體很大,則:
- 編譯器會在 caller 中分配臨時內存
- 傳遞 指向該內存的隱藏指針
十、小結圖示(建議結合圖)
函數參數 ABI 層調用過程:main:┌─────────────┐│ 棧變量 a │ ← &a└─────────────┘│▼
foo(a, &a):┌──────────────┐│ 寄存器傳參 │ ← rdi = a, rsi = &a│ 棧幀建立 ││ 局部變量空間 │└──────────────┘