
✨前言:C++作为一门接近底层的编程语言,其内存管理机制是程序员必须掌握的核心技能。相比于C语言的malloc/free,C++的new/delete不仅能够进行内存分配,还能自动调用构造函数和析构函数,为面向对象编程提供了更强大的支持。本文将深入探讨C++内存管理的各个方面,帮助读者建立系统的内存管理知识体系。 📖专栏:【C++成长之旅】
我们先来看下面的一段代码和相关问题:
int globalVar = 1;
static int staticGlobalVar = 1;
void test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}问题:

解答:


对于C语言中的内存管理方式可参考:【C语言中动态内存管理】,有助于我们在偏底层一点的了解C++内存管理方式。
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间
int* ptr3 = new int[3];
// 动态申请10个int类型的空间并初始化
int* ptr4 = new int[3] {1,2,3};
delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;
}调试结果:


注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
可能对于内置类型的操作并没有显著的区别,接下来,我们来看一下new和delete操作自定义类型吧。
以Date类为类:
class Date
{
public:
Date(int year = 2025, int month = 9, int day = 9)
{
std::cout << "Date(int year = 2025, int month = 9, int day = 9)" << std::endl;
}
~Date()
{
std::cout << "~Date()" << std::endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 1;
};
int main()
{
Date* pd = new Date;
delete pd;
return 0;
}
申请连续空间并初始化:
class Date
{
public:
Date(int year = 2025, int month = 9, int day = 9)
{
std::cout << "Date(int year = 2025, int month = 9, int day = 9)" << std::endl;
}
~Date()
{
std::cout << "~Date()" << std::endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 1;
};
int main()
{
Date* pd = new Date[2]{ {1,1,1},{2,2,2} };
delete[] pd;
return 0;
}对于这段代码,我们来强调一个点,也算是对类和对象的复习:
// 严格按C++标准分析(不考虑编译器优化):
Date* pd = new Date[2]{ {1,1,1},{2,2,2} };
// 这行代码的执行过程:
// 1. 调用operator new[]分配容纳2个Date对象的连续内存空间
// 2. 对于数组中的每个元素:
// a. 首先用初始化列表 {1,1,1} 构造临时Date对象(调用普通构造函数)
// Date temp1(1, 1, 1);
// b. 然后用临时对象拷贝构造数组第一个元素(调用拷贝构造函数)
// new(pd) Date(temp1);
// c. 销毁临时对象(调用析构函数)
// temp1.~Date();
//
// d. 用初始化列表 {2,2,2} 构造临时Date对象(调用普通构造函数)
// Date temp2(2, 2, 2);
// e. 然后用临时对象拷贝构造数组第二个元素(调用拷贝构造函数)
// new(pd+1) Date(temp2);
// f. 销毁临时对象(调用析构函数)
// temp2.~Date();在这里,已经看得很明白了: new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数。 对于内置类型前面说过,是几乎是一样的。
所以可以看出: 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。 我们将会从底层两者的实现来进行了解
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 new函数的实现知道,operator new 实际也是通过malloc来申请空间
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;
}对于operator delete的实现貌似不像operator new一样,好像没有调用free(),实则不然,我们来看一下free()的实现
#define free(p) _free_dbg(p, _NORMAL_BLOCK)可见,operator delete 最终是通过free来释放空间的。
那说这两个函数有什么用呢,那我们接着来看一下new和delete的实现原理。
如果申请的是内置类型的空间,new 和 malloc、delete 和 free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间;new[] 和 delete[] 申请的是连续空间;new 在申请空间失败时会抛出异常,malloc 会返回 NULL。operator new 函数申请空间;operator delete 函数释放对象的空间。operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请;operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。我们前面说,new和delete,new[]和delete[],匹配起来使用。那如果不匹配呢,我们来看看,对于Date类我这里就不在下面的代码中写了。
int main()
{
Date* pd = new Date[10];
delete pd;
return 0;
}结果:

为什么: 这里我们转到反汇编瞅一眼,看不懂没关系,只需看懂一个点:

这里的size就是我们要开的内存空间,按常理说,我们要开120字节就行呀,为什么多了4字节呢?

此时用delete来释放空间,释放的就是pd此时指向的空间,故会出错。
所以,我们要new和delete,new[]和delete[],匹配起来使用。
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。 使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表 使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。 我们以上面的Date类来举例:
class Date
{
public:
Date(int year = 2025, int month = 9, int day = 9)
{
std::cout << "Date(int year = 2025, int month = 9, int day = 9)" << std::endl;
}
~Date()
{
std::cout << "~Date()" << std::endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 1;
};
int main()
{
//两者等价
//Date* pd = new Date;
//delete pd;
//注意: pd现在指向的只不过是与Date对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Date* pd = (Date*)operator new(sizeof(Date));
new(pd)Date;
pd->~Date();
operator delete(pd);
return 0;
}现在我们作为了解就行。
特性 | malloc/free | new/delete |
|---|---|---|
本质 | 函数 | 操作符 |
初始化 | 不会初始化 | 可以初始化 |
空间大小计算 | 需手动计算并传递 | 只需指定类型或对象个数 |
返回值类型 | void*,使用时必须强转 | 与类型匹配,无需强转 |
失败处理 | 返回 NULL,必须判空 | 抛出异常,需捕获处理 |
自定义类型对象的处理 | 只开辟空间,不调用构造/析构函数 | 调用构造函数初始化,调用析构函数清理 |

掌握好C++内存管理,不仅能够写出更健壮、高效的代码,还能深入理解面向对象编程的底层机制,为后续学习更高级的C++特性打下坚实基础。