x86-64系统V ABI中返回值的处理机制
在System V ABI规范下,x86-64编译器使用 rax 和 rdx 寄存器来返回前两个整型值。对于结构体,需遵循分类规则:小尺寸结构体归为 INTEGER,大尺寸则归为 MEMORY。这决定了返回值是直接传递,还是通过传入 rdi 的隐藏指针方式返回。
我们先看一个最简单的返回 int 类型的例子:
one_plus_one():
mov DWORD PTR [rbp-4], 2
mov eax, DWORD PTR [rbp-4]
ret
值被加载到 eax(即 rax 的低32位)。对于64位类型,则直接使用完整的 rax 寄存器。
小型结构体(≤16字节):寄存器返回
小于等于16字节的小型结构体可直接通过 rax 和 rdx 返回。以一个16字节的结构体为例:
struct nums {
std::int64_t first{};
std::int64_t second{};
};
nums construct() {
nums ret{10, 120};
return ret;
}
生成的汇编代码如下:
construct():
mov QWORD PTR [rbp-16], 10
mov QWORD PTR [rbp-8], 120
mov rax, QWORD PTR [rbp-16]
mov rdx, QWORD PTR [rbp-8]
ret
字段按顺序排列:first 存入 rax,second 存入 rdx。内存布局采用小端序,栈向下增长。
- INTEGER分类:最多容纳两个64位字段,可直接放入寄存器。
- 对齐要求:字段必须对齐至8字节边界。
- 限制条件:总大小不得超过16字节,否则无法直接返回。
大型结构体:通过 rdi 传递隐藏地址
超过16字节的结构体被归类为 MEMORY。调用方需预先分配内存,并将地址作为隐藏的第一个参数传入 rdi。
示例:一个32字节的结构体:
struct many_nums {
std::int64_t first{};
std::int64_t second{};
std::int64_t third{};
std::int64_t fourth{};
};
many_nums construct_scary() {
many_nums temp{10, 20, 30, 40};
return temp;
}
在 construct_scary 函数中:
construct_scary():
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov QWORD PTR [rax], 10
mov QWORD PTR [rax+8], 20
mov QWORD PTR [rax+16], 30
mov QWORD PTR [rax+24], 40
ret
mov rax, [rbp-8] 这行是 -O0 编译级别下的产物。优化后代码会直接使用 rdi,无需额外移动。
在 main 函数中:
main:
sub rsp, 32
lea rax, [rbp-32]
mov rdi, rax
call construct_scary
ret
- 内存分配:可通过
sub rsp, size在栈上分配,或在堆上分配。 - 地址传递:调用前将地址写入
rdi。 - MEMORY分类:System V ABI 对大于16字节或非
INTEGER类型的类型使用此分类。
返回值分类规则详解
System V ABI 定义了三种返回类型分类:
- INTEGER:大小不超过16字节,且仅包含 ≤2个64位标量或聚合字段 → 直接通过
rax/rdx返回。 - MEMORY:大小超过16字节,或包含 >2个128位的SSE打包类型,或非
INTEGER类型 → 使用隐藏的rdi参数传递地址。 - SSE:浮点数类型最大128位 → 通过
xmm0返回(本文不展开讨论)。
GCC、Clang 等主流编译器均依据这些规则生成代码。调试时建议使用 -O0,以便清晰观察中间步骤。
核心要点总结
- 小于等于16字节的整型返回值,优先使用
rax/rdx寄存器。 - 对于
MEMORY类型,调用方需通过rdi提供内存空间。 - System V ABI 保证了 Linux 与 macOS 平台间行为的一致性。
- 启用优化(如
-O2及以上)可自动移除冗余的mov指令。 - 建议使用
objdump -d或 Godbolt Compiler Explorer 验证实际生成的汇编代码。
— Editorial Team
暂无评论。