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

内存释放机制以及栈和堆(c++)

内存释放机制

代码演示

#include <iostream>using namespace std;int main() {int* ptr = new int(1314);*ptr = 520;cout << *ptr << endl;delete ptr;ptr = NULL;return 0;
}

这段代码主要演示了动态内存分配和释放的基本操作:

  1. 使用new int(1314)动态分配了一个 int 类型的内存空间,并初始化为 1314,返回的指针存储在ptr
  2. 通过指针ptr修改了该内存空间的值为 520
  3. 输出该内存空间的值(会输出 520)
  4. 使用delete ptr释放动态分配的内存
  5. 将指针ptr设置为NULL(在 C++11 及以后建议使用nullptr
  1. 动态内存分配与释放的对应关系

    • new 对应 delete(用于单个对象)
    • new[] 对应 delete[](用于数组)
  2. 内存释放的过程

    • 调用对象的析构函数(对于类类型)
    • 通知操作系统该内存空间可以重新分配给其他部分使用
    • 注意:释放内存后,指针本身并不会被删除,只是它指向的内存不再有效,这也是为什么通常会将指针设置为nullptr的原因
  3. 内存泄漏

    • 如果动态分配的内存没有被释放,或者使用了错误的释放方式,会导致内存泄漏
    • 例如:用new分配的内存用free()释放,或用new[]分配的内存用delete释放
  4. 悬空指针

    • 内存被释放后,原来指向它的指针就变成了悬空指针
    • 访问悬空指针可能导致不可预测的行为,因此释放内存后将指针置空是良好的编程习惯

补充内存泄漏

1. 可用内存逐渐耗尽,程序性能下降

内存泄漏会导致程序持续占用动态分配但未释放的内存,随着运行时间增长,可用内存会逐渐减少。当系统物理内存不足时,操作系统会启用虚拟内存(如磁盘交换区),而虚拟内存的读写速度远低于物理内存,会导致程序运行卡顿、响应缓慢,甚至出现 “假死” 现象。

2. 长期运行的程序崩溃

对于需要持续运行的程序(如服务器、后台服务、嵌入式设备固件等),内存泄漏的影响会逐渐累积。例如:

  • 服务器程序若存在内存泄漏,可能在运行数天或数周后因内存耗尽而崩溃,导致服务中断;
  • 嵌入式设备(如智能手表、工业控制器)内存资源有限,泄漏可能更快导致设备死机或功能异常。

3. 系统稳定性受损

当单个程序因内存泄漏占用大量内存时,会抢占其他进程的内存资源,导致整个系统可用内存不足。此时:

  • 其他程序可能无法正常分配内存,出现启动失败或运行异常;
  • 操作系统可能触发 “内存不足” 保护机制,强制终止占用内存过高的进程(包括泄漏程序或其他无辜进程),进一步破坏系统稳定性。

4. 资源浪费与成本增加

泄漏的内存属于 “无效占用”—— 程序不再使用这部分内存,却不让系统回收再分配给其他需求。这会导致硬件资源利用率下降,尤其在服务器集群等场景中,可能需要额外投入硬件资源(如增加内存)来缓解泄漏带来的压力,间接增加运营成本。

5. 调试与维护难度增大

内存泄漏的隐蔽性较强:

  • 短期内可能无明显症状,难以在测试阶段发现;
  • 泄漏位置可能分散在复杂代码中(如循环、回调函数、异常分支),需借助专门工具(如 Valgrind、Visual Leak Detector)检测,增加调试成本;
  • 泄漏可能与特定操作路径相关(如用户高频操作触发),重现和定位问题的难度较大。

6. 用户体验恶化

对于客户端应用(如桌面软件、手机 App),内存泄漏可能导致:

  • 界面卡顿、操作延迟;
  • 频繁触发系统内存警告;
  • 极端情况下被系统强制关闭,导致用户数据丢失或操作中断,严重影响用户信任。

栈和堆

1. 栈(Stack)的特点与示例

栈是由编译器自动管理的内存区域,主要用于存储:

  • 函数的局部变量
  • 函数参数
  • 函数调用时的返回地址等临时信息

栈的核心特点

  • 分配和释放由编译器自动完成(函数进入时分配,函数退出时释放)
  • 内存大小固定(通常较小,取决于系统)
  • 遵循 "先进后出"(FILO)的顺序
错误示例:返回栈上变量的指针

栈上的局部变量在函数执行结束后会被自动释放,此时返回其指针会导致 "悬空指针"(指向已释放的内存),访问该指针会产生未定义行为。

#include <iostream>
using namespace std;// 错误:返回栈上局部变量的指针
int* getStackVar() {int stackVar = 10;  // stackVar在栈上分配return &stackVar;   // 函数结束后,stackVar会被自动释放
}int main() {int* ptr = getStackVar();// 此时ptr指向的内存已被释放,访问结果不确定cout << "访问栈上已释放的变量:" << *ptr << endl;  // 危险!可能输出乱码或崩溃return 0;
}

解释stackVargetStackVar函数的局部变量,存储在栈上。当函数执行结束时,栈会自动回收stackVar的内存。此时ptr指向的是一块已释放的内存,后续访问该指针属于非法操作,可能导致程序崩溃或数据错误。

正确示例:返回栈上变量的值(值传递)

虽然不能返回栈上变量的指针 / 引用,但可以返回变量的值(编译器会创建副本),这是安全的:

#include <iostream>
using namespace std;// 正确:返回栈上变量的值(会创建副本)
int getStackValue() {int stackVar = 10;  // 栈上的变量return stackVar;    // 返回值的副本,原变量在函数结束后释放
}int main() {int value = getStackValue();cout << "栈上变量的值(副本):" << value << endl;  // 输出10,安全有效return 0;
}

解释:函数返回stackVar时,编译器会创建该值的副本并返回给main函数,原栈上的stackVar虽然被释放,但副本不受影响,因此访问是安全的。

2. 堆(Heap)的特点与示例

堆是由程序员手动管理的内存区域,主要用于存储:

  • 动态分配的变量(生命周期由程序员控制)
  • 需要在多个函数间共享的数据
  • 大小不确定或需要长期存在的数据

堆的核心特点

  • 分配和释放需手动操作(用new分配,delete释放)
  • 内存大小灵活(通常远大于栈)
  • 没有固定的分配 / 释放顺序,容易产生内存碎片
正确示例:返回堆上变量的指针

堆上的变量不会随函数结束而释放,因此可以安全返回其指针(但需记得手动释放,否则会内存泄漏):

#include <iostream>
using namespace std;// 正确:返回堆上变量的指针(需手动释放)
int* getHeapVar() {int* heapVar = new int(20);  // 用new在堆上分配内存,初始化为20return heapVar;              // 堆内存不会随函数结束释放
}int main() {int* ptr = getHeapVar();cout << "堆上的变量:" << *ptr << endl;  // 输出20,安全有效delete ptr;  // 手动释放堆内存,避免泄漏ptr = nullptr;  // 避免悬空指针return 0;
}

解释heapVar是用new在堆上分配的变量,其生命周期不受函数getHeapVar的影响。函数返回指针后,main函数可以正常访问该内存。但必须用delete手动释放,否则会导致内存泄漏。

3. 栈与堆的核心区别总结

特性栈(Stack)堆(Heap)
管理方式编译器自动分配 / 释放程序员手动分配(new)/ 释放(delete
生命周期随函数调用开始 / 结束newdelete(或程序结束)
内存大小较小(通常几 MB)较大(可达 GB 级别)
分配效率高(直接操作栈指针)低(需查找空闲内存块)
碎片问题无(自动连续分配)有(频繁分配 / 释放会产生碎片)
安全风险返回指针 / 引用会导致悬空指针忘记释放会导致内存泄漏

关键结论

  • 栈上的局部变量不能返回其指针或引用(函数结束后内存被释放,导致悬空指针)。
  • 栈上的变量可以返回其值(编译器会创建副本,安全有效)。
  • 堆上的变量可以返回其指针(内存需手动释放,否则会泄漏)。
  • 实际开发中,应优先使用栈(自动管理,安全高效),仅在需要动态控制生命周期时使用堆,并严格遵循 "谁分配谁释放" 的原则。

感谢大家的阅读,点点关注和收藏每天8点持续更新!!!

http://www.dtcms.com/a/395934.html

相关文章:

  • PCL基础:点云体积计算,若需更精确的体积估算,可采用 Alpha Shape 或 Marching Cubes 等方法重建表面后再进行积分计算。
  • OSPF实验-20250922
  • Python控制流概述
  • 【LLM学习】【Ollama】四、MCP
  • 5G RedCap模组:轻量化5G技术的商业化实现
  • 深入探索卷积神经网络:从基础到高级架构(二)
  • 什么是DeepSeek-V3.1-Terminus版本?
  • 【C语言代码】堵车问题
  • A Survey of Zero-Shot Learning: Settings, Methods, and Applications
  • Windows连接Linux做开发的安装和配置
  • 【C++】lambda表达式类型相关问题
  • HTML应用指南:利用GET请求获取全国大疆限飞区域shp图层信息
  • Nginx进阶(二)
  • VSCode+WSL+cpolar:打造跨平台的随身Linux开发舱
  • Redis高可用方案:主从复制、哨兵与集群
  • STM32_03_库函数
  • SGP30气体传感器详解 (STM32)
  • stm32 BootLoader之检查栈顶地址是否合法(否则无法跳转到APP程序)
  • PyTorch 神经网络工具箱学习总结
  • 容器化 Spring Boot 应用程序
  • python 打包单个文件
  • Python自学21 - Python处理图像
  • 比特浏览器的IP适配性
  • LLHTTP测试
  • 2. 基于IniRealm的方式
  • 第三十四天:矩阵转置
  • MySQL执行计划:如何发现隐藏的性能瓶颈?
  • embedding多模态模型
  • ⚡ GitHub 热榜速报 | 2025 年 09 月 第 3 周
  • Synchronized的实现原理:深入理解Java线程同步机制