【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++动态开辟内存的方法更加方便。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
25天前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
|
6天前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
201 4
|
3月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
210 22
|
3月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
3月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
63 0
【C++打怪之路Lv6】-- 内存管理
|
3月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
3月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
104 1
|
3月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
605 1
|
3月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
125 3