当前位置:首页 > 学习资源 > 讲师博文 > ARM 函数调用时参数和返回值到底放在哪里?AAPCS 协议详解与实验平台实战

ARM 函数调用时参数和返回值到底放在哪里?AAPCS 协议详解与实验平台实战 时间:2026-11-06      来源:华清远见

在嵌入式 C 语言开发中,我们经常编写函数并传递参数。但在底层的 ARM 汇编层面,这些参数是如何从调用者传递给被调用者的?返回值又是如何带回的?这一切都遵循着一套严格的“交通规则”——AAPCS。本文将结合华清远见元宇宙实验平台的 Cortex-M4 内核寄存器视图,带你通过可视化的方式彻底搞懂 ARM 函数调用的底层秘密。

一、什么是 AAPCS?

AAPCS,全称是 ARM Architecture Procedure Call Standard(ARM 架构过程调用标准)。如果把 CPU 的寄存器比作高速公路上的车道,那么 AAPCS 就是交通法规。它规定了在 ARM 处理器上,当一段代码(调用者)去呼叫另一段代码(被调用者)时,双方必须遵守的底层协议。如果没有这套标准,不同的编译器(如GCC和Keil)生成的代码将无法互相配合,C语言也无法顺利调用汇编代码。

核心作用:

统一参数传递规则:规定前几个参数放寄存器,剩下的放栈。

统一返回值规则:规定结果放在哪个寄存器带回来。

寄存器保护责任:明确哪些寄存器由调用者保存,哪些由被调用者保存(防止数据被意外覆盖)。

二、ARM 函数调用时,参数和返回值到底放在哪里?

在经典的32位ARM (ARMv7-M,如 Cortex-M3/M4) 架构中,AAPCS对16个通用寄存器(R0-R15)的使用进行了严格分工。我们可以结合华清远见元宇宙实验平台左侧的“Cortex-M4 处理器内核寄存器”面板来直观理解。

1. 参数的传递位置

前 4 个参数:优先通过通用寄存器 R0, R1, R2, R3 传递。

第 1 个参数 → R0

第 2 个参数 → R1

第 3 个参数 → R2

第 4 个参数 → R3

超过 4 个的参数:第 5 个及之后的参数,将通过栈(Stack) 进行传递(通常遵循从右向左的顺序压栈)。

在实验平台的寄存器面板中,你可以清晰地看到 R0 - R3 位于“通用寄存器”区域的最上方。这四个寄存器是函数调用的“VIP 通道”,存取速度最快。

 

2. 返回值的位置

位及以内的整型/指针数据:通常通过寄存器 R0 返回。

位数据(如 long long、double):通过寄存器 R0 和 R1 联合返回。

大型结构体:通常由调用者在栈上预留空间,并将该空间的地址作为隐藏的第一个参数(放入R0)传递给被调用函数。

 

3. 特殊寄存器的角色

除了通用寄存器,还有两个关键寄存器参与函数调用:

SP (R13):栈指针。用于存放超过4个的参数以及局部变量。

LR (R14):链接寄存器。非常重要!它存放了函数执行完毕后的返回地址。当函数执行完 BX LR 指令时,CPU就会跳回这里继续执行。

 

三、实战演练:在元宇宙实验平台验证 AAPCS

为了验证上述理论,我们编写一段简单的C语言测试代码,并在华清远见元宇宙实验平台中进行单步调试观察。

测试代码

#include <stdio.h>

// 定义一个有5个参数的函数,测试寄存器传参和栈传参的边界

int add_numbers(int a, int b, int c, int d, int e) 

{

    int sum = a + b + c + d + e;

    return sum;

}

int main( ) 

{

    // 传入 1, 2, 3, 4, 5

    int result = add_numbers(1, 2, 3, 4, 5);

    

    printf("Result: %d\n", result);

    return 0;

}

调试观察步骤

请在实验平台中加载上述代码,并在 add_numbers 函数入口处设置断点,观察以下现象:

1. 观察 R0 - R3(前四个参数)

当程序停在 add_numbers 函数内部时,查看左侧寄存器面板:

R0 的值应该是 0x00000001 (对应参数 a)

R1 的值应该是 0x00000002 (对应参数 b)

R2 的值应该是 0x00000003 (对应参数 c)

R3 的值应该是 0x00000004 (对应参数 d)

 

2. 观察栈内存(第五个参数)

由于只有 4 个参数寄存器,第 5 个参数 e (值为 5) 会被压入栈中。

查看 SP (R13) 寄存器的值,这是当前栈顶地址。

在平台的内存查看窗口中,找到 SP 指向的地址(通常是 SP + 某个偏移量,取决于编译器对齐),你会发现数值 5 存储在那里。
 

3. 观察 LR (R14)(返回地址)

查看 LR (R14) 寄存器。它的值是一个内存地址(例如 0x0800xxxx)。

这个地址指向的是 main 函数中调用 add_numbers 之后的那条指令(即 printf 那一行)。这就是函数执行完后“回家”的路。
 

4. 观察返回值

单步执行直到函数返回。

你会发现在函数返回后,R0 的值变成了 0x0000000F (即十进制的 15),这就是计算结果 1+2+3+4+5 的返回值。

 

函数调用全过程图解:

以 int result = add(1, 2, 3, 4, 5, 6); 为例,展示 AAPCS 下的完整调用流程:

┌─────────────────────────────────────────────────────

│                    调用者(Caller)                                                                                       │

│  MOV R0, #1          ← 第1个参数                                                                                  │

│  MOV R1, #2          ← 第2个参数                                                                                  │

│  MOV R2, #3          ← 第3个参数                                                                                  │

│  MOV R3, #4           ← 第4个参数                                                                                 │

│  PUSH {R5, R6}        ← 第5、6个参数压栈                                                                   │

│  BL add               ← 跳转,LR = 返回地址                                                                    │

│  ADD R5, R0, #0       ← 从 R0 取返回值                                                                       │

│  POP {R5, R6}         ← 恢复栈                                                                                       │

└─────────────────────────────────────────────────────┘

                            ↓ 调用 ↓

┌──────────────────────────────────────────────────────

│                   被调用者(Callee)                                                                                      │

│  PUSH {R4-R7, LR}       ← 序言:保存 callee-saved 寄存器 + LR                                 │

│  ADD R0, R0, R1         ← 从 R0, R1 取前两个参数                                                         │

│  ADD R0, R0, R2         ← 从 R2 取第3个参数                                                                 │

│  ADD R0, R0, R3         ← 从 R3 取第4个参数                                                                 │

│  LDR R4, [SP, #xx]       ← 从栈中取第5、6个参数                                                          │

│  ADD R0, R0, R4        ← 累加                                                                                        │

│  POP {R4-R7, PC}       ← 尾声:恢复寄存器,弹出 LR→PC 返回                               │

└────────────────────────────────────────────────────┘
 

四、实战建议及总结

实战建议

参数尽量不超过4个:超过4个会触发栈操作,增加指令数和执行时间。

位参数对齐:long long 类型必须放在偶数寄存器对(R0:R1 或 R2:R3),不能放在 R1:R2。

调试 HardFault 时查栈帧:当程序崩溃时,通过查看 SP 指向的栈帧内容,可以还原调用链和参数值。

中断服务函数特殊处理:中断发生时,硬件自动将 R0–R3、R12、LR、PC、xPSR 压栈(共8个字),ISR 返回时自动出栈。
 

总结

通过华清远见元宇宙实验平台的可视化调试,我们将抽象的 AAPCS 协议变成了看得见的数据流动:

掌握 AAPCS 不仅有助于理解 C 语言底层机制,更是进行嵌入式汇编混合编程、RTOS 任务切换以及系统级 Debug 的基石。

上一篇:PCB设计中线宽、线距与过孔的基本概念

下一篇:自动驾驶中的视觉感知与传感器融合是什么?

戳我查看嵌入式每月就业风云榜

点我了解华清远见高校学霸学习秘籍

猜你关心企业是如何评价华清学员的

干货分享
相关新闻
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2026 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号-5,京公海网安备11010802025203号

回到顶部