返回

arm64 程序调用约定详解:参数传递机制深入分析

IOS

了解 ARM64 程序调用约定:揭开参数传递的秘密

在计算机的幕后,程序调用约定扮演着至关重要的角色,决定着函数如何交流和传递信息。对于 ARM64 架构,这套规则尤为重要,它确保了代码的可移植性和效率。让我们踏入 ARM64 程序调用约定的世界,了解它如何影响函数的调用和参数传递。

寄存器与堆栈:数据传递的工具

ARM64 架构拥有 31 个通用寄存器,它们是快速访问数据的关键。当一个函数被调用时,前 8 个寄存器(r0-r7)负责传递函数参数。剩余的参数和局部变量则通过堆栈传递,这是一个专门用于存储临时数据的内存区域。

参数传递:寄存器和堆栈的协作

ARM64 程序调用约定规定了以下参数传递规则:

  • 前 8 个整数参数通过寄存器(r0-r7)传递。
  • 超过 8 个的剩余整数和浮点参数通过堆栈传递。
  • 传递给函数的第一个参数(r0)通常是一个函数指针或指向结构的指针。

这种规则确保了前 8 个最常用的参数可以通过快速访问的寄存器传递,而较少使用的参数则通过堆栈传递,从而优化了程序性能。

函数返回值:寄存器中的结果

函数通过以下方式之一返回结果:

  • 整数结果:存储在 r0 寄存器中。
  • 结构结果:存储在堆栈中,并通过 r0 寄存器返回指向它的指针。

这确保了函数可以轻松地返回各种类型的数据,并保持代码的可读性和可维护性。

平台差异:遵守操作系统规则

虽然 Android、iOS 和 Linux 等操作系统都遵循 ARM64 程序调用约定,但它们也有一些自己特定的规则:

  • Android:
    • 使用 r9 寄存器存储线程局部存储(TLS)指针。
    • 使用 r10 寄存器传递额外的参数(可选)。
  • iOS:
    • 使用 r11 寄存器传递额外的参数。
    • 使用 r12 寄存器存储异常帧指针。
  • Linux:
    • 使用 r9 寄存器存储系统调用号。
    • 使用 r10 寄存器存储错误码(可选)。

了解这些平台差异对于在不同操作系统上开发和移植代码至关重要。

示例代码:代码中的实践

以下示例代码展示了如何在 ARM64 中编写和调用一个函数:

// 函数原型
int add(int a, int b) {
    return a + b;
}

// 函数调用
int main() {
    int x = 10;
    int y = 20;
    int result = add(x, y);
    return result;
}

汇编代码:

// 函数 add
add:
    add x0, x0, x1  // 将 x0 和 x1 相加,结果存储在 x0 中
    ret             // 返回

// 函数 main
main:
    mov x0, 10      // 将 10 存储在 x0 中
    mov x1, 20      // 将 20 存储在 x1 中
    bl add          // 调用函数 add
    mov x0, x0      // 将 add 的返回值存储在 x0 中
    ret             // 返回

常见问题解答

  1. 为什么寄存器传递比堆栈传递更快?
    寄存器比堆栈访问速度更快,因为它不需要内存寻址和读取操作。

  2. 如何处理超出 8 个的参数?
    超出 8 个的参数通过堆栈传递。

  3. 函数指针的特殊性是什么?
    函数指针通常传递在 r0 中,因为它作为函数调用的入口点。

  4. 为什么不同的操作系统有自己的规则?
    每个操作系统都有自己的特定特性和需求,这需要反映在程序调用约定中。

  5. 如何在不同的平台上编写可移植代码?
    熟悉每个平台的特定规则,并使用条件编译或预处理程序来适应差异。

结论

ARM64 程序调用约定为函数调用和参数传递提供了明确的规则。了解这些规则对于编写高效且可移植的 ARM64 代码至关重要。通过掌握寄存器和堆栈的协同作用以及不同平台的差异,开发人员可以创建健壮且高性能的 ARM64 程序,满足现代计算的不断增长的需求。