函数调用时:参数、局部变量和栈帧放在哪里
2026/8/24大约 2 分钟
函数调用时:参数、局部变量和栈帧放在哪里
函数调用时,参数和局部变量不一定都在栈上。
以 ARM Cortex-M 常见的 AAPCS 调用约定为例:
前4个参数 → R0 ~ R3
更多参数 → 栈
局部变量 → 通常在栈帧中一、函数参数放在哪里?
例如:
int add(int a, int b)
{
return a + b;
}调用:
add(10, 20);在 ARM 常见调用约定下:
10 → R0
20 → R1函数进入后直接使用:
R0 → a
R1 → b如果参数超过 4 个:
func(a, b, c, d, e, f);通常:
a → R0
b → R1
c → R2
d → R3
e → 栈
f → 栈不是“函数参数都在栈上”。在 ARM Cortex-M 中,前几个参数优先使用寄存器。
二、局部变量放在哪里?
例如:
void func(void)
{
int a = 10;
int b = 20;
// ...
}需要保存的局部变量通常放在当前函数的栈帧中。
函数进入时可能执行类似:
SUB SP, SP, #8给局部变量留出空间。
函数返回:
ADD SP, SP, #8释放这块空间。
所以:
进入函数
↓
SP向下移动
↓
获得局部变量空间
↓
执行函数
↓
恢复SP
↓
空间释放三、栈帧里有什么?
一个典型函数栈帧可以粗略理解为:
高地址
┌─────────────────────┐
│ 栈上传入的参数 │
├─────────────────────┤
│ 返回地址 / 保存信息 │
├─────────────────────┤
│ 保存的寄存器 │
├─────────────────────┤
│ 局部变量 │
├─────────────────────┤
│ 对齐填充 │
└─────────────────────┘
低地址但实际布局会根据:
- 编译器
- 优化等级
- ABI
- 是否需要保存寄存器
而变化。
所以不要死记“栈帧一定长这样”。
四、返回地址从哪里来?
ARM Cortex-M 调用函数时通常使用:
BL funcBL 会把返回地址保存到:
LR(Link Register)函数返回时:
BX LR回到调用者。
如果函数内部又调用其他函数,LR 可能需要先保存到栈中。
五、static 局部变量不在栈上
void func(void)
{
static int count = 0;
count++;
}这里的 count 不属于当前函数栈帧。
通常:
有初值
→ .data
初始为0
→ .bss它的生命周期贯穿整个程序运行。
所以:
普通局部变量
→ 栈
→ 每次进入函数重新建立
static 局部变量
→ .data / .bss
→ 整个程序生命周期六、最终记忆
ARM Cortex-M:
前4个参数
→ R0 ~ R3
更多参数
→ 栈
局部变量
→ 通常在栈帧
static 局部变量
→ .data / .bss
返回地址
→ 通常先放 LR
→ 必要时保存到栈函数调用的核心:寄存器负责快速传参,栈负责保存无法放进寄存器的参数、局部变量和需要保存的调用现场。

