返回首页

返回 x86-64 结构体:寄存器和 MEMORY ABI

本文详解 x86-64 中按照 System V ABI 从函数返回基本类型和结构体的机制。描述 INTEGER 和 MEMORY 分类规则、rax/rdx 寄存器的使用以及隐藏 rdi 参数。无优化的汇编示例。

x86-64:如何通过 rax、rdx 和 rdi 返回结构体
Advertisement 728x90

x86-64系统V ABI中返回值的处理机制

在System V ABI规范下,x86-64编译器使用 raxrdx 寄存器来返回前两个整型值。对于结构体,需遵循分类规则:小尺寸结构体归为 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 寄存器。

Google AdInline article slot

小型结构体(≤16字节):寄存器返回

小于等于16字节的小型结构体可直接通过 raxrdx 返回。以一个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 存入 raxsecond 存入 rdx。内存布局采用小端序,栈向下增长。

Google AdInline article slot
  • 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 函数中:

Google AdInline article slot
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

Advertisement 728x90

继续阅读