C++ 动态内存

C++ 动态内存

一、程序内存分区概述

C++ 程序运行时内存主要分为:栈、堆、全局/静态区、常量区、代码区 。

日常开发中动态内存管理核心围绕**栈(Stack)与堆(Heap)**展开。


二、栈(Stack)详细说明

  1. 管理方式

    由操作系统自动分配、自动回收,无需程序员手动干预。

  2. 存储内容

    函数局部变量、函数形参、临时变量、局部实例化对象。

  3. 生命周期

    绑定函数作用域,函数执行结束、作用域销毁时,栈内存立刻自动释放。

  4. 空间特性

  • 空间容量小、上限固定(系统默认数MB);
  • 内存地址连续,地址由高向低增长;
  • 空间不足会触发栈溢出(Stack Overflow)。
  1. 访问效率

    内存连续、CPU寻址高效,读写速度快。

  2. 线程属性

    每个线程拥有独立栈空间,线程私有,不存在多线程竞争问题。


三、堆(Heap)详细说明

  1. 管理方式

    由程序员手动控制,通过 new / new[] 申请,delete / delete[] 手动释放。

  2. 存储内容

    动态变量、动态数组、大型对象、需要跨作用域长期使用的数据。

  3. 生命周期

    不受函数作用域限制,内存分配后会一直常驻,直到手动释放或程序进程结束;

    未手动释放会造成内存泄漏。

  4. 空间特性

  • 空间极大,受系统虚拟内存限制,可灵活分配大容量内存;
  • 内存地址不连续,频繁分配释放易产生内存碎片;
  • 分配失败返回空指针或抛出异常,无溢出风险。
  1. 访问效率

    分配与释放开销大,碎片化内存导致访问效率低于栈。

  2. 线程属性

    全局共享内存,多线程同时操作堆空间需要加锁保证线程安全。


四、堆与栈核心区别对照表

对比维度 栈(Stack) 堆(Heap)
内存管理者 系统自动管理 程序员手动管理
分配方式 自动分配 new 动态手动分配
释放方式 作用域结束自动释放 必须手动 delete 释放
空间大小 容量小、固定上限 空间大、支持动态扩容
内存布局 连续内存空间 非连续内存,易产生碎片
分配速度 快速高效 分配释放较慢
生命周期 随函数/作用域销毁 生命周期自定义,跨函数可用
典型用途 局部小变量、临时数据 大容量数据、动态数组、长生命周期对象
溢出问题 易发生栈溢出 无溢出,仅分配失败
地址增长方向 高地址 → 低地址 低地址 → 高地址
线程归属 线程私有 进程全局共享

五、C++ 动态内存:new 与 delete

5.1 核心作用

  • new:在堆区手动分配内存,自动调用类的构造函数;
  • delete:释放堆区内存,自动调用类的析构函数。

5.2 单个内存分配与释放

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    // 堆上分配单个 double 内存
    double* pvalue = new double;
    *pvalue = 29494.99;

    cout << "堆内存变量值:" << *pvalue << endl;

    // 释放堆内存
    delete pvalue;
    pvalue = nullptr; // 释放后置空,避免野指针
    return 0;
}

5.3 内存分配失败判断

堆内存不足时 new 会分配失败,增加空指针判断保证程序健壮性:

cpp 复制代码
double* p = nullptr;
if (!(p = new double))
{
    cout << "内存不足,分配失败!" << endl;
    exit(1);
}

六、动态数组内存分配

6.1 一维动态数组

cpp 复制代码
// 分配堆数组
int* arr = new int[10];

// 使用数组
for(int i = 0; i < 10; ++i)
    arr[i] = i;

// 数组释放必须使用 delete[]
delete[] arr;
arr = nullptr;

6.2 二维动态数组

cpp 复制代码
// 二维数组开辟
int row = 3, col = 4;
int** mat = new int*[row];
for(int i = 0; i < row; ++i)
{
    mat[i] = new int[col];
}

// 二维数组释放
for(int i = 0; i < row; ++i)
{
    delete[] mat[i];
}
delete[] mat;
mat = nullptr;

6.3 关键规则

  • new[] 开辟数组,必须配套 delete[] 释放;
  • 混用 new / delete[] 会导致内存泄漏、程序崩溃。

七、对象动态内存分配

使用 new / delete 操作类对象,会自动触发构造与析构:

cpp 复制代码
#include <iostream>
using namespace std;

class Box
{
public:
    Box()
    {
        cout << "调用构造函数" << endl;
    }
    ~Box()
    {
        cout << "调用析构函数" << endl;
    }
};

int main()
{
    // 堆上创建 4 个对象,调用 4 次构造
    Box* boxArr = new Box[4];

    // 释放数组对象,调用 4 次析构
    delete[] boxArr;
    boxArr = nullptr;
    return 0;
}

八、new/delete 与 malloc/free 对比

  1. malloc / free
  • C 语言标准库函数;
  • 仅负责原始内存的申请与释放;
  • 不调用构造函数、析构函数,不适合 C++ 面向对象开发。
  1. new / delete
  • C++ 专属运算符;
  • 分配内存 + 自动调用构造函数;
  • 释放内存 + 自动调用析构函数;
  • 类型安全,支持重载,C++ 项目优先使用。

九、动态内存使用规范与注意事项

  1. 配对原则:new 对应 delete,new[] 对应 delete[],禁止混用;
  2. 内存泄漏:堆内存必须手动释放,长期遗漏会导致程序内存占用持续上涨;
  3. 野指针规避:内存释放后,立即将指针赋值为 nullptr;
  4. 禁止重复释放、悬空指针访问已释放内存;
  5. 场景选择:
    • 短期、局部、小数据 → 使用栈内存;
    • 大数据、动态长度、跨作用域 → 使用堆内存;
  6. 多线程开发中,共享堆资源需做好同步加锁。

十、代码示例:栈内存 vs 堆内存

cpp 复制代码
// 栈内存:自动分配自动释放
void stackDemo()
{
    int a = 10;         // 栈变量
    char buf[1024];     // 栈数组
    Box box;            // 栈对象
}
// 函数结束,所有栈变量自动销毁

// 堆内存:手动管理
void heapDemo()
{
    int* pNum = new int(100);
    Box* pBox = new Box;
    int* pArr = new int[20];

    // 手动释放
    delete pNum;
    delete pBox;
    delete[] pArr;
}
相关推荐
古法老登瞎折腾8 小时前
大数据智能运维实战(十一):运维 MCP 的工程化——权限、限流、审计与错误处理
java·大数据·运维·spring ai·mcp
智能运维指南9 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
yz_aiks10 小时前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
java·spring boot·大模型·ai agent·spring ai
金銀銅鐵10 小时前
[Java] 用 GUI 展示 EnumMap 增删键值对时的内部变化
java·后端·ai编程
学长毕设10 小时前
基于SpringBoot的上门洗车服务系统设计与实现
java·spring boot·后端
jason成都10 小时前
RTKLIB-Java 多基线联合RTK:FIX率从0%到91.5%的三层根因修复
java·gnss·rtklib·rtklib_java
xiaoye-duck10 小时前
《C++ 工具库实战》gflags 命令行参数解析库详解
c++·第三方库
鱼宵10 小时前
Stream 终端操作一次讲透:collect、count、reduce 怎么选
java·stream·终端操作
白山编程大哥10 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
狂奔的sherry11 小时前
C++ 宏生成访问函数的代价:访问权限、调试和类型检查
c++·宏·编译·预处理器·类型检查