首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++】内存管理

【C++】内存管理

作者头像
苏兮
发布2026-01-13 17:37:52
发布2026-01-13 17:37:52
610
举报

内存管理

前言:C++作为一门接近底层的编程语言,其内存管理机制是程序员必须掌握的核心技能。相比于C语言的malloc/free,C++的new/delete不仅能够进行内存分配,还能自动调用构造函数和析构函数,为面向对象编程提供了更强大的支持。本文将深入探讨C++内存管理的各个方面,帮助读者建立系统的内存管理知识体系。 📖专栏【C++成长之旅】

一、C/C++内存分布

我们先来看下面的一段代码和相关问题:

代码语言:javascript
复制
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语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理。

3.1 new/delete操作内置类型
代码语言:javascript
复制
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操作自定义类型吧。

3.2 new和delete操作自定义类型

以Date类为类:

代码语言:javascript
复制
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;
}

在这里插入图片描述
在这里插入图片描述

申请连续空间并初始化:

代码语言:javascript
复制
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;
}

对于这段代码,我们来强调一个点,也算是对类和对象的复习:

代码语言:javascript
复制
// 严格按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不会。

四、operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。 我们将会从底层两者的实现来进行了解

4.1 operator new

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常(后续会针对抛异常单独写篇文章),现在我们只需知道如果申请内存失败了,这里会抛异常。

代码语言:javascript
复制
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来申请空间

4.2 operator delete
代码语言:javascript
复制
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()的实现

代码语言:javascript
复制
#define   free(p)   _free_dbg(p, _NORMAL_BLOCK)

可见,operator delete 最终是通过free来释放空间的。

那说这两个函数有什么用呢,那我们接着来看一下new和delete的实现原理。

五、new和delete的实现原理

5.1 内置类型

如果申请的是内置类型的空间,newmallocdeletefree 基本类似,不同的地方是:

  • new/delete 申请和释放的是单个元素的空间;
  • new[]delete[] 申请的是连续空间;
  • new 在申请空间失败时会抛出异常,malloc 会返回 NULL
5.2 自定义类型
  • new 的原理

  1. 调用 operator new 函数申请空间;
  2. 在申请的空间上执行构造函数,完成对象的构造。
  • delete 的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作;
  2. 调用 operator delete 函数释放对象的空间。
  • new T[N] 的原理

  1. 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请;
  2. 在申请的空间上执行 N 次构造函数。
  • delete[] 的原理

  1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理;
  2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。
5.3 不同编译器的处理

我们前面说,new和delete,new[]和delete[],匹配起来使用。那如果不匹配呢,我们来看看,对于Date类我这里就不在下面的代码中写了。

代码语言:javascript
复制
int main()
{
    Date* pd = new Date[10];

    delete pd;
    return 0;
}

结果:

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

此时用delete来释放空间,释放的就是pd此时指向的空间,故会出错。

所以,我们要new和delete,new[]和delete[],匹配起来使用

六、定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。 使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表 使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。 我们以上面的Date类来举例:

代码语言:javascript
复制
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的区别

  • 共同点
  • 都是从堆上申请空间;
  • 都需要用户手动释放。
  • 不同点

特性

malloc/free

new/delete

本质

函数

操作符

初始化

不会初始化

可以初始化

空间大小计算

需手动计算并传递

只需指定类型或对象个数

返回值类型

void*,使用时必须强转

与类型匹配,无需强转

失败处理

返回 NULL,必须判空

抛出异常,需捕获处理

自定义类型对象的处理

只开辟空间,不调用构造/析构函数

调用构造函数初始化,调用析构函数清理


七、总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存管理
    • 一、C/C++内存分布
    • 二、C语言内存管理方式
    • 三、C++内存管理方式
      • 3.1 new/delete操作内置类型
      • 3.2 new和delete操作自定义类型
    • 四、operator new与operator delete函数
      • 4.1 operator new
      • 4.2 operator delete
    • 五、new和delete的实现原理
      • 5.1 内置类型
      • 5.2 自定义类型
      • 5.3 不同编译器的处理
    • 六、定位new表达式(placement-new)
    • 七、malloc/free和new/delete的区别
    • 七、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档