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}     ; 恢复现场并返回

栈的用途

  1. 保存返回地址:LR寄存器压栈
  2. 保护寄存器:防止被调用函数破坏调用者的数据
  3. 局部变量存储:函数内的自动变量
  4. 参数传递:当寄存器不够时通过栈传递
  5. 中断处理:保存处理器状态

栈指针操作

显式调整

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):最后调用的函数最先返回
  • 自动管理:编译器生成栈操作代码
  • 大小固定:栈溢出会导致程序崩溃

栈帧的特点

  • 嵌套结构:栈帧在栈内嵌套排列
  • 动态创建/销毁:函数调用时创建,返回时销毁
  • 独立空间:每个栈帧内的变量相互隔离

总结

简单比喻: - = 一整栋办公大楼 - 栈帧 = 大楼里的单个办公室

所有函数共享同一个栈,但每个函数调用都有自己独立的栈帧!