3.15 参数传递
x86-64 使用寄存器 + 栈的混合方式传递函数参数。前 6 个参数通过寄存器传递,第 7 个及以后的参数通过栈传递。
寄存器参数传递规则
前 6 个整数/指针参数依次放入:
| 参数序号 | 寄存器 | 说明 |
|---|---|---|
| 第 1 个参数 | %rdi | 第一个参数 |
| 第 2 个参数 | %rsi | 第二个参数 |
| 第 3 个参数 | %rdx | 第三个参数 |
| 第 4 个参数 | %rcx | 第四个参数 |
| 第 5 个参数 | %r8 | 第五个参数 |
| 第 6 个参数 | %r9 | 第六个参数 |
参数传递示例
// C 代码:6 个参数以内的函数
long func(long a, long b, long c, long d, long e, long f) {
return a + b + c + d + e + f;
}# 调用者的设置
movq $1, %rdi # 第一个参数 a = 1
movq $2, %rsi # 第二个参数 b = 2
movq $3, %rdx # 第三个参数 c = 3
movq $4, %rcx # 第四个参数 d = 4
movq $5, %r8 # 第五个参数 e = 5
movq $6, %r9 # 第六个参数 f = 6
call func
# 被调函数内部
func:
addq %rsi, %rdi # a += b
addq %rdx, %rdi # a += c
addq %rcx, %rdi # a += d
addq %r8, %rdi # a += e
addq %r9, %rdi # a += f
movq %rdi, %rax # 返回 a
ret栈参数传递
当参数超过 6 个时,第 7 个及以后的参数按从右到左的顺序压入栈中。
栈传参规则
- 第 7 个参数在 栈顶的上方(靠近调用者的方向)
- 第 8 个参数在第 7 个参数上方的 8 字节处
- 以此类推
- 所有栈参数按 8 字节对齐
7 个及以上参数示例
// C 代码:8 个参数
long big_func(long a1, long a2, long a3, long a4, long a5, long a6,
long a7, long a8) {
return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8;
}# 调用者的设置
big_func:
# 栈参数:从右到左压栈
pushq $8 # 第 8 个参数(先压入)
pushq $7 # 第 7 个参数(后压入,在栈顶)
# 注意:实际编译器常用 movq 到特定偏移位置
# 寄存器参数
movq $1, %rdi # a1
movq $2, %rsi # a2
movq $3, %rdx # a3
movq $4, %rcx # a4
movq $5, %r8 # a5
movq $6, %r9 # a6
call big_func
# 编译器通常的优化做法(不 push,而是预分配空间后 mov):
# subq $16, %rsp
# movq $7, 8(%rsp) # a7 在 rsp+8
# movq $8, (%rsp) # a8 在 rsp
# 被调函数内部
big_func:
# 访问寄存器参数
addq %rsi, %rdi # a1 += a2
addq %rdx, %rdi # a1 += a3
addq %rcx, %rdi # a1 += a4
addq %r8, %rdi # a1 += a5
addq %r9, %rdi # a1 += a6
# 访问栈参数(注意返回地址占 8 字节,所以从 rsp+8 开始)
addq 8(%rsp), %rdi # a1 += a7
addq 16(%rsp), %rdi # a1 += a8
movq %rdi, %rax # 返回 a1
ret栈参数的内存布局
当被调函数刚执行时(call 之后,栈帧建立之前):
高地址
┌─────────────────────┐
│ a8 │ ← 调用者栈帧
├─────────────────────┤
│ a7 │
├─────────────────────┤ ← %rsp + 16
│ 返回地址 │ ← call 压入
├─────────────────────┤ ← %rsp + 8
│ (被调函数的栈帧) │
└─────────────────────┘ ← %rsp
低地址
注意:%rsp 指向栈顶。返回地址在 %rsp,a7 在 %rsp + 8,a8 在 %rsp + 16。
如果被调函数执行了 pushq %rbp:
┌─────────────────────┐
│ a8 │
├─────────────────────┤
│ a7 │
├─────────────────────┤ ← %rbp + 24
│ 返回地址 │
├─────────────────────┤ ← %rbp + 8
│ 保存的 %rbp │ ← %rbp 指向这里
├─────────────────────┤ ← %rsp
浮点数参数
浮点数参数使用不同的寄存器传递:
| 参数序号 | 寄存器(浮点) |
|---|---|
| 第 1-8 个 | %xmm0 ~ %xmm7 |
超过 8 个浮点参数时也通过栈传递。
返回值传递
| 返回值类型 | 存放位置 |
|---|---|
| 整数/指针 | %rax |
| 浮点数 | %xmm0 |
| 128 位整数 | %rdx(高 64 位)+ %rax(低 64 位) |
| 大型结构体 | 通过隐式指针参数返回 |
返回大型结构体
struct Big {
long data[100];
};
struct Big create_big() {
struct Big b;
// ... 初始化 b ...
return b;
}# 实际调用时,编译器会传递一个隐藏参数:
# %rdi = 指向调用者分配的 Big 结构体的指针
# 被调函数将结果直接写入该内存位置
create_big:
# %rdi 指向调用者提供的缓冲区
# 将结果写入 (%rdi), 8(%rdi), ...
movq $0, (%rdi)
# ...
movq %rdi, %rax # 返回缓冲区地址
ret参数传递总结
| 参数范围 | 传递方式 | 寄存器/位置 |
|---|---|---|
| 第 1-6 个参数 | 寄存器 | %rdi, %rsi, %rdx, %rcx, %r8, %r9 |
| 第 7+ 个参数 | 栈 | 返回地址上方,按从右到左的顺序 |
| 返回值 | 寄存器 | %rax |
| 浮点参数 1-8 | 寄存器 | %xmm0 ~ %xmm7 |
| 浮点返回值 | 寄存器 | %xmm0 |
相关笔记
-
- 参数传递使用的寄存器
-
- 栈帧中的参数区域
-
- 调用过程中寄存器的保存策略
链接到
- 上一个知识点:3.14 控制转移
- 下一个知识点:3.16 调用者与被调用者保存寄存器