【c++】动态内存管理

简介: 本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。

前言

       之前在C语言当中,我们学习了动态内存管理的相关知识以及使用malloc/calloc/realloc/free函数实现堆区中动态内存的分配:


https://developer.aliyun.com/article/1632498?spm=a2c6h.24874632.expert-profile.34.369929bee9zTHh


而对于c++而言,我们有了新的方式来实现动态内存分配,它们就是new和delete。本篇文章我们就来详细探讨一下它们的使用方法以及c++/c语言实现动态内存管理的区别。


一、内存区域分布

       首先我们来看一段代码并尝试解决以下问题:


1. GlobalVar是全局变量,存储在数据段(静态区),选C。

2. staticGlobalVar是静态全局变量,也存储在数据段(静态区),选C。

3. staticVar是静态的局部变量,存储在数据段(静态区),选C。

4. localVar是局部变量,存储在栈区当中,选A。

5. num1是一个局部变量(存储着数组首元素的地址),存储在栈区中,选A。

6. char2与num1相同,是局部变量,选A。

   *char2表示字符数组中的字符‘a’,存储在栈区中,选A。

7. pchar3是指向常量字符串首字符的指针,是一个局部变量,选A。

   *pchar3表示常量字符串中的字符‘a’,属于常量,存储在代码段(常量区),选D。

8. ptr1是一个局部指针变量,存储在栈区,选A。

   *ptr1是动态开辟的内存区域中的值,存储于堆区中,选B。


这里重点关注一下*char2和*pchar3的存储位置:char2所在语句的含义是将字符串"abcd"存储到字符数组当中,本质是存储到了变量当中,所以解引用之后得到的字符肯定是在栈区中存储的;而pchar3所在语句是将常量字符串"abcd"中首字符的地址存放于指针变量当中,该指针变量的值是存储在栈区当中的,但是解引用之后得到的字符是常量,所以*pchar3肯定存储于常量区。


接下来,我们画图表示一下内存区域的分布:



       c++的动态内存分配与c语言相同,也是在堆区中进行操作的。


二、c++中的动态内存管理方式

       之前在c语言当中,我们使用malloc/calloc/realloc/free函数来实现动态内存管理,但由于使用方式较为麻烦(例如要手动计算申请的内存大小、检查返回值等),所以c++引入了两个操作符,便于我们更高效地实现动态内存管理:new和delete


       接下来我们从代码角度来解释这两个关键字的使用方法。


1. new与delete对内置类型的操作

int main()
{
    int* p1 = new int;//动态申请一个int类型的空间
 
    int* p2 = new int(10);//动态申请一个int类型的空间,并初始化为10
 
    int* p3 = new int[10];//动态申请10个int类型的空间
 
    int* p4 = new int[10] {10};//动态申请10个int类型的空间,并将第一个元素初始化为10,其余元素为0
    //这里的初始化规则与数组定义时的初始化相同
 
    //释放内存
    delete p1;
    delete p2;
    delete[] p3;
    delete[] p4;
    return 0;
}

可以看到,我们使用new操作符申请内存时,不仅不用sizeof来计算申请所需的空间大小,而且还能对申请的空间进行初始化,十分方便。这里要注意:当我们释放连续的空间时,delete之后要加上“ [ ] ”


2. new与delete对自定义类型的操作

       new和delete申请和释放自定义类型的空间时,与内置类型的语法是相同的。当我们使用new/delete操作自定义类型时,它们与malloc/free最大的区别是:new在申请内存空间之后还会调用构造函数对该空间进行初始化;delete会调用析构函数,然后释放内存空间。而malloc/free只会开辟空间,并不会调用这两种函数


代码示例:

#include <iostream>
using namespace std;
 
class A
{
public:
    A(int a = 10, int c = 20)
        :_a(a)
        ,_c(c)
    {
        cout << "调用构造函数" << endl;
    }
    ~A()
    {
        cout << "调用析构函数" << endl;
    }
    void Print() const
    {
        cout << _a << endl;
        cout << _c << endl;
    }
private:
    int _a;
    int _c;
};
 
int main()
{
    A* p = new A{ 3,5 };//动态申请一个A类型的空间,并且调用构造函数初始化
 
    p->Print();//打印内容
 
    delete p;//调用析构函数,然后释放空间
    return 0;
}

运行结果:



三、operator new函数和operator delete函数

       operator new函数和operator delete函数是c++提供的全局函数,当我们使用new或者delete操作符时,它们就会调用这两个函数来实现相关功能。以下是这两个函数的底层实现:

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);
}
 
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 new函数体当中,首先调用了malloc函数来申请内存空间,如果申请成功则直接返回,否则就会启动相关应对措施;在operator delete函数体中,我们可以看到一个函数“_free_dbg”,它其实就是free的底层实现。所以说operator new使用了malloc来开辟内存,operator delete使用了free来释放内存,只不过在其中添加了一些异常处理机制等,使得内存开辟更加完善。

       了解了这两个函数的运行机制,我们由此总结出newdelete的实现原理:


四、new和delete的实现原理

1. 内置类型

       对于内置类型而言,new/delete与malloc/free基本类似,不同的地方是:在申请空间时可以进行初始化,并且new在空间申请失败时会抛出异常,而malloc会返回空指针


2. 自定义类型

       对于自定义类型,它的实现逻辑就比较复杂了,我们逐一分析:


1. new:首先调用operator new函数申请内存空间,然后调用构造函数,完成初始化


2. delete:首先调用析构函数,对开辟的内存进行资源清理,然后调用operator delete函数释放内存


3. new[]:首先调用 operator new[ ] 函数申请多个对象的内存空间(该函数中调用了operator new),然后调用N次构造函数,完成初始化


4.delete[]:首先调用N次析构函数清理资源,然后调用 operator delete[ ] 函数释放空间(该函数中调用了operator delete)


五、定位new表达式

       我们都知道,当对象被创建的时候,会自动调用构造函数。那么我们能否在一块已有的内存区域上显示调用构造函数构造对象呢?语法上是不允许显示调用构造函数的,但是定位new表达式可以做到:

class A
{
public:
    A(int a = 10, int c = 20)
        :_a(a)
        ,_c(c)
    {
        cout << "调用构造函数" << endl;
    }
    ~A()
    {
        cout << "调用析构函数" << endl;
    }
    void Print() const
    {
        cout << _a << endl;
        cout << _c << endl;
    }
 
    int _a;
    int _c;
};
 
int main()
{
    A* p = (A*)malloc(sizeof(A));//申请内存空间,但不调用构造函数
    new(p)A();//使用定位new表达式调用构造函数
 
    p->Print();//打印一下成员
 
    p->~A();//显示调用析构函数
    return 0;
}

运行结果:


可以看到,我们成功使用定位new表达式调用了构造函数并且为成员变量设置初始值。定位new表达式在实现内存池或缓存区等高级内存管理策略时非常有用。定位new表达式的语法是:


new(ptr)Class(参数)


这里的ptr表示指向该内存区域的指针,Class是类名。当构造函数中有非缺省参数时,需要我们在类名之后的括号中传参。


定位new表达式的注意事项:

1. 常规new表达式既负责分配内存,还负责构造对象;而定位new表达式只负责构造对象。所以在使用定位new表达式之前,要确保以及分配好足够的内存。

2. 使用定位new表达式调用构造函数后,如果我们不再使用该对象,要记得主动调用其析构函数并释放内存。

六、malloc/free和new/delete的区别总结

       最后,我们总结一下malloc/free和new/delete的区别:


共同点:都是从堆区申请空间,并且使用结束后要进行手动释放。

不同点:

1. malloc和free是函数,而new和delete是操作符。

2. malloc申请的空间不会进行初始化,而new可以初始化。

3. malloc申请空间时,需要手动计算申请的空间大小;而new申请时只需要说明类型与个数即可。

4. malloc申请空间返回的指针是void* 类型,需要进行强转,而new不需要。

5. malloc申请失败会返回NULL,所以使用时必须检查返回值;而new申请失败会抛出异常,无需检查返回值。

6. 对于自定义类型空间的开辟,malloc和free只会开辟/销毁对应的内存空间,而new和delete会调用构造函数/析构函数完成初始化/资源清理操作。


总结

       今天我们学习了C++中动态内存管理的方法、它们的实现原理以及它们与c语言内存管理方法的区别。我们能够感觉到,相比c语言,C++动态开辟内存的方法更加方便。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
10天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
14天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
5天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
|
10天前
|
人工智能 运维 双11
2024阿里云双十一云资源购买指南(纯客观,无广)
2024年双十一,阿里云推出多项重磅优惠,特别针对新迁入云的企业和初创公司提供丰厚补贴。其中,36元一年的轻量应用服务器、1.95元/小时的16核60GB A10卡以及1元购域名等产品尤为值得关注。这些产品不仅价格亲民,还提供了丰富的功能和服务,非常适合个人开发者、学生及中小企业快速上手和部署应用。
|
5天前
|
人工智能 自然语言处理 前端开发
用通义灵码,从 0 开始打造一个完整APP,无需编程经验就可以完成
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。本教程完全免费,而且为大家准备了 100 个降噪蓝牙耳机,送给前 100 个完成的粉丝。获奖的方式非常简单,只要你跟着教程完成第一课的内容就能获得。
|
21天前
|
自然语言处理 数据可视化 前端开发
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
合合信息的智能文档处理“百宝箱”涵盖文档解析、向量化模型、测评工具等,解决了复杂文档解析、大模型问答幻觉、文档解析效果评估、知识库搭建、多语言文档翻译等问题。通过可视化解析工具 TextIn ParseX、向量化模型 acge-embedding 和文档解析测评工具 markdown_tester,百宝箱提升了文档处理的效率和精确度,适用于多种文档格式和语言环境,助力企业实现高效的信息管理和业务支持。
3945 4
从数据提取到管理:合合信息的智能文档处理全方位解析【合合信息智能文档处理百宝箱】
|
10天前
|
算法 安全 网络安全
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
2024阿里云11.11金秋云创季活动火热进行中,活动月期间(2024年11月01日至11月30日)通过折扣、叠加优惠券等多种方式,阿里云WoSign SSL证书实现优惠价格新低,DV SSL证书220元/年起,助力中小企业轻松实现HTTPS加密,保障数据传输安全。
530 3
阿里云SSL证书双11精选,WoSign SSL国产证书优惠
|
9天前
|
数据采集 人工智能 API
Qwen2.5-Coder深夜开源炸场,Prompt编程的时代来了!
通义千问团队开源「强大」、「多样」、「实用」的 Qwen2.5-Coder 全系列,致力于持续推动 Open Code LLMs 的发展。
|
16天前
|
安全 数据建模 网络安全
2024阿里云双11,WoSign SSL证书优惠券使用攻略
2024阿里云“11.11金秋云创季”活动主会场,阿里云用户通过完成个人或企业实名认证,可以领取不同额度的满减优惠券,叠加折扣优惠。用户购买WoSign SSL证书,如何叠加才能更加优惠呢?
995 3
|
14天前
|
机器学习/深度学习 存储 人工智能
白话文讲解大模型| Attention is all you need
本文档旨在详细阐述当前主流的大模型技术架构如Transformer架构。我们将从技术概述、架构介绍到具体模型实现等多个角度进行讲解。通过本文档,我们期望为读者提供一个全面的理解,帮助大家掌握大模型的工作原理,增强与客户沟通的技术基础。本文档适合对大模型感兴趣的人员阅读。
447 18
白话文讲解大模型| Attention is all you need