C/C++ 内存管理详解:从内存分布到 new/delete 底层原理

结论先行

new 的底层本质是两步操作:调用 operator new 申请内存(底层用 malloc),然后在这块内存上执行构造函数。

而 delete 恰好相反:先执行析构函数,再调用 operator delete(底层用 free)释放内存。理解这一层,就理解了 C++ 内存管理的核心设计。

一、C/C++ 程序的内存分布

程序运行时,内存被划分为几个功能不同的区域:

区域 存储内容 管理方式 增长方向
栈(Stack) 非静态局部变量、函数参数、返回值 编译器自动分配/释放 向下(高地址→低地址)
堆(Heap) malloc/new 动态分配的内存 程序员手动分配/释放 向上(低地址→高地址)
数据段(静态区) 全局变量、static 变量 程序启动时分配,退出时释放 ---
代码段(常量区) 可执行代码、字符串常量 只读 ---

一个经典示例

cpp 复制代码
int globalVar = 1;                    // 数据段(已初始化)
static int staticGlobalVar = 1;      // 数据段(已初始化)

void func() {
    static int staticVar = 1;        // 数据段(已初始化)
    int localVar = 1;                // 栈
    char charArray[] = "abcd";       // 栈(内容从常量区拷贝过来)
    char *pChar = "abcd";            // pChar 在栈,"abcd" 在常量区
    int *pInt = (int*)malloc(sizeof(int));  // pInt 在栈,*pInt 在堆
}

关键区分 :charArray 和 pChar 看起来都在存字符串,但 charArray 的数组内容在栈 上(可以修改),而 pChar 指向的字符串在常量区(修改是未定义行为)。

二、C 语言动态内存管理:malloc/calloc/realloc/free

2.1 四个函数的区别

函数 功能 初始化
malloc(size) 申请 size 字节连续内存 不初始化(内容随机)
calloc(n, size) 申请 n×size 字节内存 初始化为 0
realloc(ptr, new_size) 调整已有内存大小 新扩部分不初始化
free(ptr) 释放内存 ---

2.2 关键细节

malloc 不初始化内存,这是很多 bug 的源头。例如:

c 复制代码
int *y = malloc(n * sizeof(int));
// y[i] 是随机值!
for (i = 0; i < n; i++)
    y[i] += A[i][j] * x[j];  // 错!y[i] 未初始化

realloc 的陷阱 :如果原内存后面没有足够空间,realloc 会在新地址分配内存并拷贝原数据 ,返回新指针,原指针可能被自动释放 。所以永远不要写成 ptr = realloc(ptr, new_size),如果 realloc 失败会丢失原指针。

free 之后置 NULL :free(p); p = NULL; 是防御性编程的好习惯,可以避免 double-free。

三、C++ 的升级:new/delete 做了什么

3.1 为什么需要 new/delete

malloc/free 在 C++ 中有两个致命问题:

  1. 不调用构造函数/析构函数:malloc 只分配内存,不会初始化对象;free 只释放内存,不会清理对象资源
  2. 类型不安全 :malloc 返回 void*,需要手动强转,无法在编译期检查类型

3.2 new 的底层两步

cpp 复制代码
A* p = new A(10);

等价于:

cpp 复制代码
// 第一步:申请内存(调用 operator new)
void* raw = operator new(sizeof(A));

// 第二步:在 raw 内存上执行构造函数
A* p = new(raw) A(10);  // 定位 new

而 operator new 的底层就是 malloc:

cpp 复制代码
void* operator new(size_t size) {
    void* p = malloc(size);
    if (!p) throw std::bad_alloc();  // 失败抛异常,不返回 NULL
    return p;
}

3.3 delete 的底层两步

cpp 复制代码
delete p;

等价于:

cpp 复制代码
p->~A();              // 第一步:执行析构函数
operator delete(p);   // 第二步:释放内存(底层 free)

3.4 new\[\] / delete\[\] 的特殊处理

对于数组:

cpp 复制代码
A* arr = new A[10];

编译器会在数组头部额外分配一个 8 字节的"cookie" ,记录元素个数 N。delete[] 读取这个 cookie,知道要调用几次析构函数:

cpp 复制代码
// delete[] 的执行逻辑
// 1. 读取 cookie,得到 N
// 2. 对 N 个元素依次执行 ~A()
// 3. operator delete 释放整块内存(包括 cookie)

这就是为什么 new[] 必须配 delete[] :如果写成 delete arr,编译器只调用一次析构函数,但释放的是整块内存(包括 cookie),导致析构次数不匹配。

四、核心对比:malloc/free vs new/delete

维度 malloc/free new/delete
本质 库函数 运算符
构造函数 不调用 调用
析构函数 不调用 调用
类型安全 返回 void*,需强转 自带类型,无需强转
大小计算 手动 sizeof 编译器自动计算
初始化 不初始化 可初始化 new int(10)
失败处理 返回 NULL 抛出 bad_alloc 异常
重载 不可重载 operator new/delete 可重载

一句话总结:malloc/free 只管"要内存、还内存",new/delete 还要"初始化对象、清理对象"。

五、总结

C/C++ 内存管理的核心脉络:

  1. 内存分布:栈(自动管理)vs 堆(手动管理),数据段/代码段(静态存储)
  2. C 的方案:malloc/calloc/realloc/free,只负责内存的分配与释放
  3. C++ 的升级 :new/delete 在 malloc/free 基础上,增加了构造函数/析构函数的自动调用
  4. 底层真相 :operator new → malloc,operator delete → free,new/delete 是"malloc/free + 对象生命周期管理"

理解这一层,面试中的 "new 和 malloc 的区别" 就不再是背诵,而是真正的理解。

参考:C++ 内存分布与 new/delete 原理 ,operator new 底层实现 ,new\[\] 的 cookie 机制 。

相关推荐
无名猿1 小时前
constexpr 能力扩张:从 C++11 到 C++20 的编译期计算
c++·性能优化·现代c++·编译期
骇客野人1 小时前
Java BIO / NIO / AIO 完整详解 + 编程技巧
java·开发语言·nio
霸道流氓气质1 小时前
OpenTelemetry 入门与实战:Java Agent、Spring Boot Starter与LLM调用追踪示例
java·开发语言·spring boot
m0_587383001 小时前
24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·spring·系统架构·需求分析
栖凤2 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
小师兄吃牛肉2 小时前
Java 同步系列之 ZooKeeper 分布式锁
java·分布式·java-zookeeper
马剑威(威哥爱编程)2 小时前
【AI全栈后端12-12】Spring Boot 3.x 到 4.x 迁移实操:Jakarta 11、Jackson 3 与 AI 2.0
java·开发语言·spring boot·后端
Gauss松鼠会2 小时前
【GaussDB】破除gaussdb ugin索引支持中文模糊查询的迷思-字符序
java·运维·服务器·网络·数据库·gaussdb·经验总结
IT研究室2 小时前
最新计算机毕业设计选题推荐-基于spring boot的心桥·心理健康综合服务平台-网站-文档指导-Java-springboot
java·spring boot·课程设计