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

内存管理(C++)

内存管理

  • 1. 内存管理概述
  • 2. 栈(Stack)内存
  • 3. 堆(Heap)内存
  • 4. new / delete 操作符
  • 5. 内存泄漏基础及成因
  • 6. 避免内存泄漏的最佳实践
    • RAII(Resource Acquisition Is Initialization)
    • 智能指针(C++11 及以后)
    • 异常安全
    • 工具检测
  • 小结

1. 内存管理概述

  • 为什么要管理内存?
    C++ 是一门允许程序员直接控制内存分配与释放的语言。合理的内存管理可以:

    • 提高性能(减少系统调用、避免频繁分配/释放)

    • 降低内存占用(释放不再使用的对象)

    • 防止程序崩溃(访问无效内存)

  • 手动 vs 自动

    • 自动管理:局部变量、函数参数等,由编译器在作用域结束时自动释放。

    • 手动管理:通过 new/delete、malloc/free(C 风格)等方式,在堆上分配和释放内存,需要程序员自己管理。

2. 栈(Stack)内存

  • 分配与释放

    • 在函数内部声明的局部变量,编译器会在函数调用时为其在栈上分配空间,函数返回时自动释放。
属性描述
速度非常快,分配/释放只需调整栈指针
生命周期与作用域一致,作用域结束即释放
大小限制受操作系统或编译器栈大小限制(几 MB)
访问方式LIFO(后进先出)
  • 示例
void foo() {int a = 42;         // 在栈上分配 sizeof(int)double arr[100];    // 在栈上分配 100 * sizeof(double)
} // 离开作用域时,a 和 arr 自动销毁

3. 堆(Heap)内存

  • 分配与释放

    • 通过 new(或 malloc)在运行时动态分配,必须显式调用 delete(或 free)释放。
属性描述
速度相对慢,需要操作系统/运行时参与
生命周期直到显式释放(或程序结束)
大小限制受限于系统可用内存,通常远大于栈
管理方式任意顺序分配/释放,容易产生碎片
  • 示例
void bar() {int* p = new int(5);       // 在堆上分配一个 int 并初始化为 5MyClass* obj = new MyClass; // 在堆上分配一个对象,调用其默认构造函数// … 使用 p、obj …delete p;    // 释放堆内存,调用对应析构delete obj;  // 释放对象内存
}

4. new / delete 操作符

  • new

    • 分配内存并调用构造函数

    • 返回指向所分配类型的指针,如果分配失败,会抛出 std::bad_alloc(可以改用 new(nothrow) 返回 nullptr)

  • delete

    • 调用析构函数并释放内存

    • 对应数组要用 delete[]

  • 示例

// 单个对象
int* pi = new int(10);
delete pi;// 对象数组
double* pd = new double[50];
delete[] pd;
  • 注意事项

    • 成对使用:new ↔ delete,new[] ↔ delete[]

    • 不要对同一块内存重复 delete(双重释放)

    • 不要释放非堆内存(如栈内存、静态/全局区)

    • 释放后指针要置 nullptr,防止悬空指针

5. 内存泄漏基础及成因

  • 什么是内存泄漏?

    • 程序在堆上分配了内存但没有对应的释放,使得这部分内存无法再被程序利用,长时间运行可能耗尽可用内存。
  • 常见成因

    • 遗漏 delete
    void f() {int* p = new int[100];// …忘记 delete[] p…
    } // 离开作用域,p 指针销毁,但堆内存仍然保留
    
    • 异常导致提前返回
    void g() {MyClass* obj = new MyClass;if (something_wrong()) return; // 忘记 delete objdelete obj;
    }
    
    • 多重所有权、混乱释放时机
      多个指针指向同一块内存,但无法确定何时释放;最终没人负责调用 delete。

    • 循环引用(智能指针场景)
      std::shared_ptr 循环引用时,引用计数永不归零。

6. 避免内存泄漏的最佳实践

RAII(Resource Acquisition Is Initialization)

  • 将资源(包括内存)封装在对象生命周期内,让构造函数申请、析构函数释放。
class Buffer {
public:Buffer(size_t n): data_(new char[n]) {}~Buffer() { delete[] data_; }
private:char* data_;
};void h() {Buffer buf(1024); // 离开作用域自动释放
}

智能指针(C++11 及以后)

  • std::unique_ptr:独占所有权,自动释放

  • std::shared_ptr:共享所有权,引用计数归零时释放

  • std::weak_ptr:配合 shared_ptr 打破循环引用

#include <memory>void k() {auto up = std::make_unique<MyClass>(args…);auto sp = std::make_shared<MyClass>(args…);
} // up、sp 离开作用域自动 delete

异常安全

  • 在有可能抛出异常的上下文中,尽量使用智能指针或容器而非裸指针。

工具检测

  • Valgrind(Linux)

  • AddressSanitizer(编译器选项 -fsanitize=address)

  • 定期进行内存检查,捕获早期泄漏。

小结

  • 栈:分配/释放迅速,生命周期受作用域控制,但容量有限。

  • 堆:容量大,可动态管理,但需显式 new/delete,且分配释放较慢。

  • new/delete:必须成对使用,注意异常安全与数组版本。

  • 内存泄漏:由遗漏释放、异常分支、循环引用等导致,会积累“僵尸”内存。

  • 最佳实践:遵循 RAII、优先使用智能指针、借助检测工具,保证代码健壮、可靠。

相关文章:

  • Polygon Miden网络:具有客户端执行的边缘区块链
  • IBM BAW(原BPM升级版)使用教程:基本概念
  • Houdini制作烟雾消散并导入UE5
  • 数字孪生储能充电站,实现智慧能源设施全景管控
  • JDK 发展历史及其版本特性
  • Python训练打卡Day17
  • 基于 AI 的工程投标六随机五区间报价得分模型模拟计算
  • 云计算与大数据进阶 | 25、可扩展系统构建
  • 力扣面试150题--对称二叉树
  • 【大模型面试每日一题】Day 10:混合精度训练如何加速大模型训练?可能出现什么问题?如何解决?
  • MYSQL的DDL语言和单表查询
  • LearnOpenGL---绘制三角形
  • 多线程网络编程:粘包问题、多线程/多进程服务器实战与常见问题解析
  • 【实战项目】简易版的 QQ 音乐:一
  • 文件上传/读取/包含漏洞技术说明
  • 大模型——GraphRAG基于知识图谱+大模型技术构建的AI知识库系统
  • 第1.3讲、什么是 Attention?——从点菜说起 [特殊字符]️
  • LeetCode 1781. 所有子字符串美丽值之和 题解
  • ultralytics框架进行RT-DETR目标检测训练
  • EASM外部攻击面管理平台
  • 金融监管总局:做好2025年小微企业金融服务工作
  • 前瞻|美联储明晨“按兵不动”几无悬念:关税战阴霾下,会否释放降息信号
  • 大规模空袭也门一日后,以军又对也门萨那机场发出撤离警告
  • 长和获准出售巴拿马运河港口以外的港口?外交部:该报道没有依据
  • 科普|治疗腰椎间盘突出症,筋骨平衡理论如何提供新视角?
  • 当AI开始谋财害命:从骗钱到卖假药,人类该如何防范?