当前位置: 首页 > news >正文

直观地理解程序的堆和栈

好的,我们来用一个直观的方式理解C++中的堆和栈。

想象一下你正在一个大办公室(你的电脑内存)里工作:

  1. 栈 (Stack) - 你的办公桌

    • 是什么:你的办公桌是你临时放东西、处理当前任务的地方。它有固定的大小,东西放上去、拿下来都很有序。
    • 特点
      • 自动管理:你开始一个新任务(调用一个函数),就在桌上腾出一块地方放这个任务的资料(函数参数、局部变量)。任务结束(函数返回),这些资料就自动被清理掉了。
      • 后进先出 (LIFO):就像你叠盘子,最后放上去的盘子最先被拿走。函数调用也是这样,最后调用的函数最先执行完毕并返回。
      • 速度快:因为管理简单(只需要移动一个指向桌子顶部的指针),分配和释放内存非常快。
      • 大小有限:你的办公桌空间是有限的(通常几MB)。
    • 什么地方用
      • 函数调用:每次调用函数,都会在栈上创建一个“栈帧”(stack frame),存放函数的参数、返回地址、局部变量等。
      • 局部变量:在函数内部定义的变量(比如 int a; char str[10];),它们的作用域只在函数内部,函数结束时自动释放。
    • 递归用的多的是什么。每次递归调用都是一个新的函数调用,都会在栈上创建一个新的栈帧。如果递归太深,办公桌就堆满了。
    • 什么时候会爆掉 (Stack Overflow - 栈溢出)
      • 无限递归或递归太深:就像你在办公桌上不停地叠文件,最终文件会塌下来。递归太深,栈空间被耗尽。
      • 在栈上分配了过大的局部变量:比如你在函数里定义了一个超大的数组 int big_array[1000000];,直接把办公桌占满了。
  2. 堆 (Heap) - 办公室的储藏室/仓库

    • 是什么:储藏室是你可以按需申请空间存放东西的地方。这个空间很大,但你需要自己去申请(new),并且用完后要自己记得去归还(delete),否则储藏室会越来越乱,最后没地方放新东西。
    • 特点
      • 手动管理:你需要显式地通过 new (或 malloc) 申请内存,并通过 delete (或 free) 释放内存。
      • 灵活:可以在程序运行时动态决定需要多大的内存,并且这块内存的生命周期可以不受函数调用的限制(可以跨函数存在)。
      • 速度相对慢:分配和释放内存比栈要复杂,需要查找合适的空闲块,所以速度慢一些。
      • 空间大:通常堆的空间远大于栈,取决于可用的系统内存。
    • 什么地方用
      • 动态分配内存:当你在编译时不知道需要多少内存,或者需要的数据量比较大(不适合放栈上),或者希望数据在函数结束后依然存在时。例如:
        int* arr = new int[size]; // size 是运行时决定的
        MyClass* obj = new MyClass();
        
      • 创建生命周期长的对象:对象需要在创建它的函数返回后依然被其他部分代码访问。
    • 什么时候会爆掉 (Heap Exhaustion / Out of Memory - 堆耗尽)
      • 内存泄漏 (Memory Leak):你不停地从储藏室拿空间 (new),但从不归还 (delete)。最终储藏室被用完,新的申请就会失败。
      • 申请过大的内存块:即使储藏室还有很多零散空间,但如果你一次性要一个超级大的连续空间,可能也找不到,导致分配失败。
      • 堆碎片化 (Heap Fragmentation):频繁申请和释放小块内存,可能导致堆中有很多不连续的小空闲块。虽然总的空闲空间可能够,但没有足够大的 连续 空间来满足某个较大的分配请求。
  3. 为什么会分这些?

    • 管理方式不同:栈的自动管理非常高效,适合生命周期短、大小固定的数据。堆的手动管理提供了更大的灵活性,但牺牲了速度和增加了程序员的责任。
    • 效率和灵活性权衡
      • 栈追求的是效率简单性,用于函数调用和局部作用域。
      • 堆追求的是灵活性,用于动态生命周期和大小不定的数据。
    • 防止内存混乱:将这两种不同使用模式的内存分开,有助于操作系统和程序更有效地管理内存。
  4. 一堆静态变量 (Static Variables) 用的多的是什么?

    • 静态变量(包括全局变量和用 static 修饰的局部变量)既不在栈上,也不在堆上
    • 它们存储在内存的另一个特定区域,通常称为 静态存储区/全局存储区 (Static/Global Storage Area) 或者有时细分为 .data 段 (已初始化的静态/全局变量) 和 .bss 段 (未初始化或初始化为0的静态/全局变量)。
    • 特点
      • 生命周期:从程序开始运行到程序结束。
      • 作用域:全局变量在整个程序中可见(除非被文件内的 static 限制),static 局部变量只在定义它的函数内可见,但其值在函数多次调用间保持。
    • 什么时候会爆掉
      • 严格来说,静态存储区的大小在编译链接时就基本确定了。如果定义了过多的、非常大的静态/全局变量,可能会导致程序映像文件过大,加载时就可能出问题(比如内存不足以加载整个程序)。运行时一般不会像栈溢出或堆耗尽那样“爆掉”,因为它的空间是预先分配好的。但如果你滥用,比如一个静态指针指向一个巨大的堆分配内存,那还是堆可能爆掉。

总结一下:

特性栈 (Stack) - 办公桌堆 (Heap) - 储藏室静态/全局区 - 公司的固定资产柜
管理自动 (编译器)手动 (new/delete)自动 (程序加载时分配,结束时释放)
速度相对慢-
大小小,固定 (OS分配)大,灵活 (受限于系统可用内存)编译时确定
生命周期函数调用期间,局部作用域手动控制,可跨函数整个程序运行期间
存放函数参数、局部变量、返回地址动态分配的对象/数据全局变量、静态变量
主要用途函数调用机制、临时数据大对象、生命周期不确定的数据程序运行期间一直需要的数据
递归用非常多较少直接用于递归本身(但递归函数内可以分配堆内存)-
“爆掉”栈溢出 (Stack Overflow) - 递归太深、局部变量太大堆耗尽 (Out of Memory) - 内存泄漏、申请过大/碎片化程序过大无法加载

希望这个办公室的类比能帮助你更好地理解!

http://www.xdnf.cn/news/937135.html

相关文章:

  • Go 语言中的内置运算符
  • LLMs之Structured Output:vLLM 结构化输出指南—从约束生成到自动解析与高效实现
  • 算法工程师认知水平要求总结
  • (javaEE)网络原理-初识 局域网和广域网 ip地址和端口号 协议 五元组 协议分层 OSI七层模型 网络数据通信的基本流程
  • (二)原型模式
  • AI短视频创富营
  • Go语言系统监控实战:gopsutil库全面解析与应用
  • nginx部署
  • K8S认证|CKS题库+答案| 8. 沙箱运行容器 gVisor
  • 安装Openstack
  • 编程技巧(基于STM32)第二章 全功能按键非阻塞式实现按键单击、双击和长按
  • 【agent开发】VS Code连接WSL失败解决
  • 实验一:数据选择器实验
  • Go语言中的if else控制语句
  • DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
  • masm32汇编实现扫雷进程注入
  • 第1课、LangChain 介绍
  • 算法-数论
  • Java线程池核心原理与最佳实践
  • 永磁同步电机参数辨识算法--IPMSM拓展卡尔曼滤波全参数辨识
  • 73常用控件_QFormLayout的使用
  • 一个自动反汇编脚本
  • 深度学习入门Day3--鱼书学习(2)
  • 前端十种排序算法解析
  • 电压型PHY芯片MDI接口设计
  • 计算机网络笔记(二十九)——5.1运输层协议概述
  • QT线程同步 QReadWriteLock并发访问
  • xtp+ctp 交易系统接口简介
  • DAX权威指南9:DAX 查询分析与优化1
  • leetcode 386. 字典序排数 中等