要深入理解 数组、指针和函数参数传递 的底层 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│ 栈帧建立 ││ 局部变量空间 │└──────────────┘