文章目录
参数传递概览
-
在调用子函数时,ARM Cortex-M3 处理器可以使用 寄存器 和 堆栈 来传递参数。具体使用哪种方式取决于传递的参数数量和调用约定(calling convention)。
-
参数传递方式
ARM Cortex-M3 处理器使用 ARM EABI (Embedded Application Binary Interface) 标准来定义参数传递的约定。根据这个约定: -
1、寄存器传递:
当函数调用时,前四个参数会优先使用寄存器 R0 到 R3 进行传递。这是因为使用寄存器传递参数比使用堆栈更快,访问寄存器的速度比访问内存(堆栈)要快。
如果参数的数量小于或等于 4,那么这些参数都会通过 R0 到 R3 传递。
如果参数的数量大于 4,则超过的参数将通过堆栈传递。 -
2、堆栈传递:
如果函数的参数超过 4 个,或者参数很大(如结构体或数组等),无法完全放入寄存器中,那么超过部分的参数会被压入堆栈。堆栈传递参数时,参数按照从右到左的顺序压入堆栈。也就是说,第一个参数(如果超出寄存器的部分)会先被压入堆栈。
堆栈传递参数具体过程
- 寄存器传递参数很好立即,寄存器传递参数不好理解,具体过程如下。
- 首先我们知道有堆栈指针SP。堆栈指针不像操作系统中每个任务有每个任务自己的堆栈,而是所有函数公有一个堆栈指针。
- 我们看汇编语言中,通过堆栈传递参数给子函数的过程:
main proc ; 将参数压入堆栈 mov ax, 5 ; 第一个参数 push ax mov ax, 3 ; 第二个参数 push ax ; 调用 add 函数 call add ; 清理堆栈(返回值从堆栈中弹出) add sp, 4 ; 每个参数占4个字节,这里加4 ; 将结果存储在 result 中 mov result, al ; 假设 add 函数返回结果在 AL 寄存器中 ; 退出程序 mov ax, 4C00h int 21h main endp add proc ; 从堆栈中弹出参数 pop ax ; ax = 第二个参数 pop bx ; bx = 第一个参数 ; 执行加法操作 add ax, bx ; ax = 第一个参数 + 第二个参数 ; 将结果返回(假设返回值在 AL 寄存器中) mov al, ah ; 结果存储在 AL 寄存器中 ; 返回到调用者 ret add endp
- 通过上述过程,我们在直接用汇编语言写代码时,很自然地使用堆栈进行参数传递。
- 那么在C语言中,如main函数,要调用下面的函数:
void example_function(int a, int b, int c, int d, int e) { int local_var1; int local_var2; // 函数体 }
- 在调用函数前,我们首先要把当前main函数下,R0-R15寄存器信息保存到堆栈中。
- 然后将前4个参数通过R0-R3寄存器进行传递,第5个参数e通过堆栈进行传递。
- 此时,堆栈指针会向下直接调整而不是通过PUSH,来为局部变量和保存的寄存器状态分配空间。调整的总量是所有参数、保存寄存器和局部变量所需空间的总和。
- 这里我们需要存储的参数e,4个字节;假设需要保存的寄存器有4个,16个字节;函数内局部变量2个,8个字节,因此堆栈需要调整的总量是28个字节。
- 在子函数内,通过调整后的堆栈指针,加上偏移量来访问局部变量和我们传递的参数。
- 执行完子函数,要返回时,需要将堆栈向上调整回去,然后回复main函数的寄存器内容。
- 整个流程汇编语言如下:
; 函数调用前的代码
push {
r4-r11, lr} ; 保存寄存器和返回地址
sub sp, sp, #28 ; 为局部变量分配 28 字节的空间
; 将额外参数压入堆栈
str r5, [sp, #20] ; 存储参数 e 到 SP + 20
str r6, [sp, #24] ; 存储参数 f 到 SP + 24
; 子函数体中的代码
ldr r4, [sp, #20] ; 从堆栈读取参数 e 到 r4
ldr r5, [sp, #24] ; 从堆栈读取参数 f 到 r5
; 函数返回前的代码
add sp, sp, #28 ; 恢复堆栈指针
pop {
r4-r11, lr} ; 恢复main函数寄存器和返回地址