C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++内存管理

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

作者:捉虫达人

C/C++ 程序内存主要分代码段、数据段、堆和栈;new/delete 底层会先调用operator new/operator delete,再调用 malloc/free,并负责对象构造与析构,‌‌‌本文介绍C/C++ 内存管理详解:从内存分布到 new/delete 底层原理,感兴趣的朋友一起看看吧

结论先行

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

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

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

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

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

一个经典示例

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 的源头。例如:

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 的底层两步

A* p = new A(10);

等价于:

// 第一步:申请内存(调用 operator new)
void* raw = operator new(sizeof(A));
// 第二步:在 raw 内存上执行构造函数
A* p = new(raw) A(10);  // 定位 new

而 operator new 的底层就是 malloc:

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

3.3 delete 的底层两步

delete p;

等价于:

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

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

对于数组:

A* arr = new A[10];

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

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

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

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

维度malloc/freenew/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 机制 。

到此这篇关于C++ 从内存分布到 new/delete 底层原理解析的文章就介绍到这了,更多相关C++内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文