Stack
目录
栈
栈是内存中的一块特殊区域,采用后进先出的原则管理数据,主要用于函数调用、局部变量存储和现场保护。
栈的核心特性
生长方向
- 向下生长:栈从高地址向低地址扩展
- 栈指针(SP):始终指向栈顶位置
高地址 → 栈底 (初始SP)
│
│ 已使用栈空间
│
↓ 栈顶 (当前SP)
低地址 → 未使用空间
栈的基本操作
PUSH - 压栈
PUSH {R0, R1, LR} ; SP减少,数据存入栈中
执行过程: 1. SP = SP - 12 (3个寄存器 × 4字节) 2. 数据按顺序存入栈中
POP - 出栈
POP {R0, R1, PC} ; 数据从栈中恢复,SP增加
栈在函数调用中的作用
函数开场
my_function:
PUSH {R4, R5, LR} ; 保存现场
SUB SP, SP, #8 ; 分配局部变量空间
栈帧布局
┌─────────────┐ ← 旧SP
│ 旧LR │
├─────────────┤
│ R5 │
├─────────────┤
│ R4 │
├─────────────┤ ← 当前SP
│ 局部变量2 │
├─────────────┤
│ 局部变量1 │
└─────────────┘ ← 新SP
函数退场
ADD SP, SP, #8 ; 释放局部变量空间
POP {R4, R5, PC} ; 恢复现场并返回
栈的用途
- 保存返回地址:LR寄存器压栈
- 保护寄存器:防止被调用函数破坏调用者的数据
- 局部变量存储:函数内的自动变量
- 参数传递:当寄存器不够时通过栈传递
- 中断处理:保存处理器状态
栈指针操作
显式调整
SUB SP, SP, #16 ; 分配16字节栈空间
ADD SP, SP, #16 ; 释放栈空间
隐式调整
PUSH {R0-R3} ; 自动 SP = SP - 16
POP {R0-R3} ; 自动 SP = SP + 16
重要特点
- 自动管理:编译器自动生成栈操作指令
- 后进先出:最后压入的数据最先弹出
- 线程私有:每个线程有自己的栈空间
- 大小有限:栈溢出会导致程序崩溃
栈 vs 栈帧
核心区别
| 特性 | 栈 (Stack) | 栈帧 (Stack Frame) |
|---|---|---|
| 数量 | 每个线程一个栈 | 每个函数调用一个栈帧 |
| 生命周期 | 线程整个生命周期 | 函数调用期间 |
| 大小 | 较大 (通常几MB) | 较小 (几十到几百字节) |
| 用途 | 所有函数调用的总容器 | 单个函数的局部工作区 |
详细解释
栈 (Stack) - "整栋大楼"
- 一个完整的记忆区域,就像一栋办公大楼
- 每个线程有自己的栈
- 从高地址向低地址生长
- 包含所有函数调用的栈帧
栈帧 (Stack Frame) - "单个办公室"
- 函数调用时创建的临时工作区,就像大楼里的一个办公室
- 每个函数调用都有独立的栈帧
- 包含该函数的局部变量、参数、返回地址等
- 函数返回时"退房",栈帧被释放
实际内存布局
多个函数调用时的栈结构
高地址 → ┌─────────────┐ ← 栈底
│ main函数 │ ← main的栈帧
│ 栈帧 │
├─────────────┤
│ funcA函数 │ ← funcA的栈帧
│ 栈帧 │
├─────────────┤
│ funcB函数 │ ← funcB的栈帧
│ 栈帧 │
├─────────────┤
│ funcC函数 │ ← funcC的栈帧
│ 栈帧 │
低地址 → └─────────────┘ ← 当前SP (栈顶)
具体示例分析
C代码示例
int funcC(int x) {
int local_c = x + 5;
return local_c;
}
int funcB(int a) {
int local_b = a * 2;
return funcC(local_b);
}
int funcA() {
int local_a = 10;
return funcB(local_a);
}
栈帧创建过程
main:
BL funcA ; 调用funcA
funcA:
PUSH {R4, LR} ; 创建funcA的栈帧
SUB SP, SP, #8 ; 为局部变量分配空间
MOV R4, #10
STR R4, [SP, #4] ; local_a = 10
BL funcB ; 调用funcB
ADD SP, SP, #8 ; 释放funcA栈帧的局部变量
POP {R4, PC} ; 释放funcA栈帧
funcB:
PUSH {R4, LR} ; 在funcA栈帧之上创建funcB栈帧
SUB SP, SP, #8
; ... funcB的代码 ...
BL funcC ; 调用funcC
ADD SP, SP, #8
POP {R4, PC}
funcC:
PUSH {R4, LR} ; 在funcB栈帧之上创建funcC栈帧
SUB SP, SP, #8
; ... funcC的代码 ...
ADD SP, SP, #8
POP {R4, PC} ; 释放funcC栈帧
栈帧的典型内容
单个栈帧的内部结构
┌─────────────┐ ← 帧指针 (FP/R11)
│ 调用者FP │ ← 前一个栈帧的帧指针
├─────────────┤
│ 返回地址 │ ← 函数返回后继续执行的位置
├─────────────┤
│ 保存的R4 │ ← 被调用者保存的寄存器
│ 保存的R5 │
│ 保存的LR │
├─────────────┤ ← 栈指针 (SP) 进入时
│ 参数5+ │ ← 多余的参数(如果有)
├─────────────┤
│ 局部变量1 │ ← 函数的自动变量
│ 局部变量2 │
├─────────────┤ ← 当前SP (函数执行中)
│ 空余 │ ← 可能用于临时存储
└─────────────┘
关键特点
栈的特点
- 后进先出 (LIFO):最后调用的函数最先返回
- 自动管理:编译器生成栈操作代码
- 大小固定:栈溢出会导致程序崩溃
栈帧的特点
- 嵌套结构:栈帧在栈内嵌套排列
- 动态创建/销毁:函数调用时创建,返回时销毁
- 独立空间:每个栈帧内的变量相互隔离
总结
简单比喻: - 栈 = 一整栋办公大楼 - 栈帧 = 大楼里的单个办公室
所有函数共享同一个栈,但每个函数调用都有自己独立的栈帧!