温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++动态内存管理实例分析

发布时间:2022-07-21 09:27:09 来源:亿速云 阅读:140 作者:iii 栏目:开发技术

本篇内容介绍了“C++动态内存管理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    C/C++ 内存分布

    我记得,在初识C语言那里就和大家分享了程序虚拟地址空间的概念,无论是C语言的nalloc函数,还是我们现在要分享的new,都是在堆区开辟空间,这一点是我们要首先记得的。

    C++动态内存管理实例分析

    C语言内存管理方式

    C语言是通过函数来经行动态的内存开辟的,标准库里面提供三个函数,这里我就不加赘述了,大家应该都是知道的。我么看看用法就可以了。

    #include <stdio.h>
    #include <assert.h>
    
    int main()
    {
    // malloc 开辟空间 不初始化
    int* p1 = (int*)malloc(sizeof(int)* 4);
    assert(p1);
    
    //calloc 开辟空间 初始化 为 0
    int* p2 = (int*)calloc(4, sizeof(int));
    assert(p2);
    // 追加 空间
    p1 = (int*)relloc(p1, sizeof(int)* 8);
    
    free(p1);
    free(p2);
    return 0;
    }

    C++内存管理方式

    C++是支持C语言的,也是说C++是可以使用这些函数的,但是除了这些函数外,C++有增加了new和delete这个两个关键字,分别对标的malloc/calloc和free,而且C++的方式比C的好用.

    C++为何增加了new 和 delete

    我们都知道,C语言的结构体里面不支持函数,所以大佬们提出了类的概念,出现了class,又害怕自己有时后可能忘记初始化和清除掉内存,就出现了构造函数和析构函数,让编译器自动调用,可以说,所有的事物的出现都是为了我们更好的使用语言,new和delete也似乎如此,C语言的动态内存开辟是有一定的麻烦的,而且对于自动类型很不友好,后面我们就会比较他们的优劣.

    我们还发现一个很直接问题,每一次开辟空间我们都要强制类型转换,而且还需要判断内存是不是究竟开出来了,这也太麻烦了,new却不会出现这种事,如果没有开辟出,编译器会抛异常,我们就不需要再自己手动检测了.

    new 一个对象

    这样,我先和大家演示内置类型,自定义类型那里我准备专门和malloc比较一下.

    #include <iostream>
    using namespace std;
    
    int main()
    {
    int* p1 = new int;
    *p1 = 10;
    cout << *p1 << endl;
    return 0;
    }

    C++动态内存管理实例分析

    我们也知道,再C++中,内置类行也被作为类了,我们可以再new的时候对它进行初始化.

    int main()
    {
    int* p = new int(0);
    cout << *p << endl;
    
    return 0;
    }

    C++动态内存管理实例分析

    new 一个数组

    new一个数组更是简单,我们直接写出来就可以了.

    int main()
    {
    int* p = new int[10]; // new 一个 10 个int 类行的空间
    return 0;
    }

    C++动态内存管理实例分析

    我们也可以在new空间的时候进行实例化,不过要显示实例化

    int main()
    {
    int* p = new int[10]{1,2,3};
    return 0;
    }

    C++动态内存管理实例分析

    delete

    大家可能发现,我上面都没有释放空间,这会造成内存泄漏,这里我们用另一个关键字delete,这里就比较简单了.

    大家可能疑惑delete[],这里我们记住就可以了,如果你要清除数组的空间,最好使用这种方式,或许对于内置类行,使用delete也可以,但是对于自定义类行可能会报错,这里我也放在后面谈.

    int main()
    {
    int* p1 = new int;
    int* p2 = new int[10]{1,2,3};
    delete p1;
    delete[] p2;
    return 0;
    }

    C++动态内存管理实例分析

    malloc & new

    我们需要对比一下malloc和new它们之间的区别,这样就可以知道C++为何这么喜欢new了.

    内置类型

    我们先下一个结论,它们两个对于内置类行除了报错之外是没有任何区别的,都不会经行初始化,这里我们现不谈报错的信息,异常和没有和大家分享.

    int main()
    {
    int* p1 = new int[10];
    
    int* p2 = (int*)malloc(sizeof(int)* 10);
    assert(p2);
    
    delete[] p1;
    free(p2);
    return 0;
    }

    C++动态内存管理实例分析

    自定义类型

    对于自定义类型,它们的差别可大了去了.

    我们先来准备一个类:

    class A
    {
    public:
    A(int a = 0,int b=0)
    :_a(a)
    , _b(b)
    {
    cout << "构造函数" << endl;
    }
    ~A()
    {
    cout << "析构函数" << endl;
    }
    private:
    int _a;
    int _b;
    };

    malloc是直接开辟空间,对于里面的构造函数是不会调用的,free的时候也不会调用析构函数

    int main()
    {
    A* aa = (A*)malloc(sizeof(A));
    free(aa);
    return 0;
    }

    C++动态内存管理实例分析

    new 和 delete会分别调用构造函数和析构函数,完成初始化

    int main()
    {
    A* aa = new A;
    delete aa;
    return 0;
    }

    C++动态内存管理实例分析

    operator new与operator delete函数

    这里一看像是new和delete的重载,记住,这不是,就是名字有点奇怪罢了.这是C++里面的全局函数,它的使用方法和malloc一样,而且作用也是有一样的,不会调用构造函数和析构函数.

    new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

    int main()
    {
    A* aa = (A*)operator new(sizeof(A));
    operator delete (aa);
    return 0;
    }

    C++动态内存管理实例分析

    原理

    通过源码我们就会发现,实际上operator new与operator delete函数 本质上是malloc和free的封装,就是报错的信息有点不同,封装的报错的信息是异常.

    operator new 的原理是 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 原理

    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); // 注意 C语言的free 就是这个函数 __FINALLY _munlock(_HEAP_LOCK); /* release other threads */ __END_TRY_FINALLY return; }

    为何出现这两个函数

    这两个函数不是给我们调用的,是为了new的底层调用的,我们new一个对象,就相当于call operator new 和 call 对象的构造函数,这才是它们出现的原因.

    大家可以看看反汇编.

    C++动态内存管理实例分析

    delete & delete[]

    这个我们可以这么理解,对于内置类型,它们就没必要讨论的,作用差不多.但是对于自定义类型就有很大的问题.

    • 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

    • 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

    大家先看看结果:

    delete[] 析构相应的的次数

    int main() { A* aa = new A[3]; delete[] aa; return 0; }

    C++动态内存管理实例分析

    delete 析构一次,还会报错

    int main() { A* aa = new A[3]; delete aa; return 0; }

    C++动态内存管理实例分析

    内存池

    这里我想提一个概念,我们都知道malloc和new都是在堆上开辟空间,如果我们要是多次的去开辟空间,效率是不是有点慢,想一想,我们一次开辟一次,开了个上千次,每次都要去申请,我们在想,能不能单独的划分出一块区域,专门提供我们想要的对象来开辟空间,这就是内存池最初的想法,大家可能会感到疑惑,内存池和堆有什么不同吗,简单来说,内存池离你近,可以提高效率。我们可以这么类比,堆就像每吨你在学校吃饭就和你老爸要钱,每顿都要,那么内存池就像月初你直接和你爸要好这个月的生活费,一月要一次,肯定是后者的效率比较高的。

    那么我们该如何使用内存池,标准库里面也提供了一个,这里我们需要在类内重写operator new与operator delete函数函数,大家先来了解一下用法就可以了,我们先不来细究,后面可能会有一个高并发内存池的项目要和大家分享,不过这个时间就有点长了。

    struct ListNode
    {
    ListNode* _next;
    ListNode* _prev;
    int _data;
    
    // 申请空间的是后去内存 池
    void* operator new(size_t n)
    {
    void* p = nullptr;
    p = allocator<ListNode>().allocate(1);
    cout << "memory pool allocate" << endl;
    return p;
    }
    void operator delete(void* p)
    {
    allocator<ListNode>().deallocate((ListNode*)p, 1);
    cout << "memory pool deallocate" << endl;
    }
    };
    class List
    {
    public:
    List()
    {
    _head = new ListNode;
    _head->_next = _head;
    _head->_prev = _head;
    }
    ~List()
    {
    ListNode* cur = _head->_next;
    while (cur != _head)
    {
    ListNode* next = cur->_next;
    delete cur;
    cur = next;
    }
    delete _head;
    _head = nullptr;
    }
    private:
    ListNode* _head;
    };
    
    int main()
    {
    List l1;
    return 0;
    }

    C++动态内存管理实例分析

    定位 new

    我们已经知道了,使用operator new开辟出的空间是不会初始化的,而且现在我们是无法通过对象来显式调用构造函数的,这也就意味着我们要是向修改成员变量,一定会破坏封装.但是C++这里也提供了一个定位new的技术可以帮助我们再次实例化,我们先来看看用法.

    class A
    {
    public:
    A(int a = 0)
    :_a(a)
    {
    }
    private:
    int _a;
    };
    
    int main()
    {
    A* a = (A*)operator new(sizeof(A));
    
    // 定位 new
    new(a)A (1);
    return 0;
    }

    C++动态内存管理实例分析

    从这里我们就可以知道了,定位new有下面两种用法

    • new(要初始化的指针) 指针解引用对应的类行 直接调用默认构造函数

    • new(要初始化的指针) 指针解引用对应的类行 (构造函数要传的参数) 调用相应的构造函数

    “C++动态内存管理实例分析”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    c++
    AI