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 机制 。

相关推荐
步行cgn7 小时前
Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示
java·spring·mvc
Zwawa7 小时前
串口为什么会丢字节:从轮询到中断和环形缓冲区
c语言·单片机·嵌入式
lgcgkCQ8 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
Wang's Blog8 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友
会编程的小孩8 小时前
C++ 定时器
c++
学到老活到老8 小时前
第 24 集:配置文件写了 80,Spring Boot 为什么读到 90?
java·后端·spring
tongyiixiaohuang8 小时前
供应商主数据同步实战:从易快报到金蝶云星空的单一策略落地
java·网络·数据库
鱼宵9 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
学长毕设9 小时前
基于SpringBoot的餐厅管理系统-附源码
java·spring boot·后端
沫璃染墨9 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html