在 C/C++ 程序开发中,内存管理始终是绕不开的核心话题。无论是手动申请堆区空间,还是借助语言特性自动管理对象生命周期,理解内存的分布与分配方式,都是写出健壮、高效代码的前提。本文将从内存分区讲起,对比
malloc/free与new/delete的差异,并深入剖析new/delete的底层实现原理,帮助大家系统掌握 C/C++ 的动态内存管理。
C/C++内存分布
我们知道内存一般分为栈区、堆区、静态区(数据段)、常量区(代码段)。
- 栈区:存储局部变量、函数形参、临时变量;
- 堆区:用于动态内存分配;
- 静态区(数据段) :存储
static修饰的变量、全局变量; - 常量区(代码段) :存储字符串常量、
const修饰的全局常量、程序代码(函数指令)。

通常堆区内存空间比较大。栈区空间是向下生长的,因为它的内存地址比较高;堆区空间是向上生长的,因为它的内存地址比较低。
在32位环境下,可以存储 232 个字节的空间,也就是 4G 的空间,在 64 位环境下就更多了。
malloc/free使用
喜欢用 malloc 和 free 的同学应该知道,调用它们要传参数,返回来的地址类型还是 void,需要指定进行强制类型转换,比较麻烦。而且在 C++ 中多了一个类类型,我们实例化一个对象的时候都要调用对应的构造函数,如果我们用 malloc 申请一块空间的话,就无法调用构造函数了,这也是一个问题。
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// malloc一个Date类对象的空间
Date* ptr = (Date*)malloc(sizeof(Date));
if(ptr==NULL)
{
perror("malloc");
exit(-1);
}
//释放该空间
free(ptr);
return 0;
}
C++ 中引入了 new 和 delete 两个关键字,用于手动向堆区申请和释放一块空间。
new/delete
new/delete使用
我们如何使用 new 和 delete 呢?是这样使用的,new + 类型,该类型是我们要申请的空间大小,后可跟 () 用于初始化我们申请的对象。
cpp
//申请创建一个int大小的空间
int* p1 = new int;
//申请创建一个int大小的空间并初始化为10
int* p2 = new int(10);
new 申请创建后就需要 delete 释放空间,使用方式为 delete 指针,该指针指向我们向堆内存中申请的空间的初始地址。
cpp
//释放空间
delete p1;
delete p2;
用 free 释放也可以,但我们一般习惯配套使用。
当我们想申请一个数组的空间,用 new 是这样申请的:new 类型[],[] 中写我们想申请该类型空间的个数,是连续的。如果我们想初始化,需要在 [] 后加 {},在 {} 里进行初始化。
cpp
//申请创建10个int大小的空间
int* p3 = new int[10];
//申请创建10个int大小的空间并进行部分初始化
int* p4 = new int[10] {1,2,3,4,5};

可以看到 p4 指向的数组中,我们未给出初始化值的部分都初始化为 0 了。
我们用 delete 来释放数组空间时就必须在 delete 后加 []。
cpp
delete[] p3;
delete[] p4;
在申请内置类型空间时 new/delete 和 malloc/free 没有什么差别,差别主要体现在申请自定义类型空间时,new 和 delete 会帮我们调用构造和析构函数,比如说之前的 Date 类。
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 申请一个Date类对象大小的空间
Date* ptr = new Date;
// 释放该块空间
delete ptr;
return 0;
}
我们运行一下就可以看到确实是调用了构造和析构函数。

但new/delete究竟是如何实现这些操作的呢?我们接下来看看它的底层原理。
new/delete 的底层原理
我们来看用 new/delete 来申请/销毁一个 Stack 类对象:
cpp
class Stack
{
public:
Stack(int n = 4)
:_array((int*)malloc(n * sizeof(int)))
, _top(0)
, _capacity(n)
{
if (_array == nullptr)
{
perror("malloc");
exit(-1);
}
}
~Stack()
{
free(_array);
}
private:
int* _array;
size_t _top;
size_t _capacity;
};
int main()
{
Stack* ptr = new Stack;
delete ptr;
return 0;
}
在这次用 new 申请空间的过程中,应该是先申请开辟了一块空间,ptr 指向该块空间。然后在这块空间上调用构造函数,该构造函数又开辟了一块空间,这个 Stack 类对象的 _array 指向该块空间。
然后在 delete 时,如果先释放掉存放 Stack 类对象的空间,那么 _array 指向的内存空间就无法被释放,造成内存泄露,所以应该是先调用相应的析构函数完成清理工作,然后再释放 ptr 指向的空间。

对于调用构造函数和析构函数我们好理解,那么它们是如何申请和释放自定义类型的空间的呢?接下来将做说明。
operator new 和 operator delete 函数
虽然 operator new 和 operator delete 很像我们之前所说过的运算符重载,但不是的,它是用来申请和释放内存空间的,它们的底层代码如下:
cpp
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
从中我们可以看到,operator new 其实也是靠 malloc 来实现的,operator delete 是靠 free 来实现的。至于使用 new 来申请一块空间为什么不用判断是否申请成功,是因为其内部有抛异常机制,这个以后会讲。
我们之前所判断的 new 是先调用函数 operator new 来申请一块类大小的空间,然后调用相应构造函数对这块空间进行初始化。然后 delete 先调用析构函数完成对对象中资源的清理,然后调用 operator delete 函数释放之前申请到的类大小的空间。我们是可以验证其过程的。
我们来到调试阶段,查看反汇编代码:

可以看到是先调用的 operator new 函数,然后调用的 Stack 函数。

对于 delete 部分我们也可以看到:

这里编译器将两个函数的调用步骤整合成一个了,我们转到X86环境下去看,比较容易观察到delete对于函数的调用。


可以看到是先调用~Stack函数,再调用的operator delete函数。

这就是new/delete的底层实现原理了。
对于数组的new/delete,则会调用operator new[]和operator delete[]函数,operator new[]就是对operator new函数的复用。
我们用new创建了n个对象,operator new[]就会调用n次operator new,然后再调用n次构造函数完成对这n个对象的初始化。
delete就会先调用n次析构函数完成对这n个对象的资源清理,然后调用operator delete[],该函数内部就会调用n次operator delete来释放资源。
定位表达式 new
C++ 提供了一种方式,可以让我们显式地调用构造函数。
比如之前的 Date 类,
cpp
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
我们在使用 malloc 来申请空间后,就可以显式调用构造函数来初始化该对象。
cpp
int main()
{
Date* ptr = (Date*)malloc(sizeof(Date));
//定位 new 表达式
new(ptr)Date(2026, 10, 12);
free(ptr);
return 0;
}
new后面的括号里跟指向对象的指针,然后跟构造函数名(初始化列表)就好了。
new/delete 比 malloc/free 好在哪?
new/delete是关键字,malloc/free是库函数,需要包含对应头文件。- 对于自定义类型,
new/delete会调用相应构造函数和析构函数,malloc/free不会。 new/delete的使用非常简单,malloc则需要传参,拿到的地址还要进行强制类型转换。new会进行抛异常,比较安全。