一句话定义:栈(Stack)和堆(Heap)是程序运行时用来放数据的两块内存区域——栈放函数调用现场和局部变量,随函数进出自动伸缩;堆放动态申请的对象,生命周期由程序员或垃圾回收器(GC)管理。
打个比方。栈像一摞托盘:你只能往最上面放、从最上面取,函数一进来就压一个“栈帧”(stack frame),里面是参数、返回地址和局部变量;函数一返回,整个托盘端走,一次性清空。所以栈的分配和释放极快,本质上只是挪一下栈顶指针。代价是这摞托盘的总高度是固定的,通常是几 MB 量级(具体以运行平台为准),摞超了就是栈溢出(stack overflow)。
堆则像一个巨大的自助仓库:你用 new 或 malloc 报个尺寸,分配器去翻找一块够大的空位,找不到就向操作系统再要一些页;东西放在哪由分配器决定,你手里只留一个地址(指针)。取用比栈慢,但容量大得多,只受可用内存限制。代价是没人替你收拾——手动 free/delete,或者靠 GC 回收。忘了释放就是内存泄漏,一次要得太多就是分配失败、进程被杀,也就是常说的 OOM。
| 栈 Stack | 堆 Heap | |
|---|---|---|
| 放什么 | 局部变量、参数、返回地址 | new/malloc 出来的对象、动态数组 |
| 分配速度 | 极快,挪指针 | 较慢,找空闲块、可能要系统调用 |
| 生命周期 | 离开作用域自动结束 | 手动释放或 GC 回收 |
| 容量 | 小且固定(MB 量级) | 大,受可用内存限制 |
| 典型故障 | 栈溢出 | 内存泄漏、OOM |
两个容易混淆的邻居。一是数据结构里的“栈”(后进先出)和调用栈确实是同一个词,调用栈就是标准 LIFO;但数据结构里的“堆”(二叉堆、优先队列)和内存的堆毫无关系,纯属重名。二是全局变量和静态变量既不在栈也不在堆,而在静态存储区。
至于“递归太深会栈溢出”,原因很直接:每层递归都要压一个栈帧,深度几千到几万就可能把托盘摞穿;忘记写终止条件则必然爆。“大对象把堆撑爆”同理:栈根本放不下大数组,你只能往堆上申请,而堆的上限就是机器剩余内存。
对从业者的实际意义:递归能改迭代就改,或自己用堆上的显式栈;大 buffer 别声明成局部变量;Python 里所有对象都在堆上,栈里只有名字到对象的引用,所以解释器对递归深度有保守限制(约千层量级,以官方文档为准);训练模型时 batch 开太大触发的 OOM,是堆的问题,跟栈无关。
