C++内存管理

0.C/C++内存分布复习

下面代码只是作为C语言基础复习,您粗浅看一下即可:

   #include 
   using namespace std;
   int globalVar = 1;//全局变量,存储在数据段
   static int staticGlobalVar = 1;//静态全局变量,存储在数据段
   void Test(void)
   {
       static int staticVar = 1;//静态局部变量,存储在数据段
       int localVar = 1;//局部变量,存储在栈
       int num1[10] = { 1,2,3,4 };//多个局部变量一次性创建,整体存储在栈
       char char2[] = "abcd";//(这里实际上是把常量区得“abcd\0”拷贝给char数组)常量表达式“abcd”,存储在代码段,但是char2数组存储在栈
       const char* pChar3 = "abcd";//常量表达式,“abcd”存储在代码段,pChar3存储在栈上
       int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1存储在栈上,*ptr1存储在堆上
       int* ptr2 = (int*)calloc(4, sizeof(int));//ptr2存储在栈上,*ptr2存储在堆上
       int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//ptr3存储在栈上,*ptr3存储在堆上
       free(ptr1);
       free(ptr3);
   }
   int main()
   {
       Test();
       return 0;
   }

1.内存管理

1.1.C语言的的动态内存管理方式

    #define SIZE 10
    int main()
    {
        int* arr = (int*)malloc(sizeof(int) * SIZE);
        if (arr == NULL)
        {
            exit(-1);
        }
        for (int i = 0; i < SIZE; i++)
        {
            arr[i] = i * i;
            printf("%d ", arr[i]);
        }
        printf("\n");
        for (int j = 0; j < SIZE; j++)
        {
            free(arr + j);
        }
        return 0;
    }

C语言在这方面使用malloccallocreallocfree来管理堆空间,这一套函数在C++中依旧可以使用,但是会有很大的问题。

    class Data
    {
    public:
        Data(int x = 10)
        {
            _x = x;
        }
    private:
        int _x;
    };
    int main()
    {
        Data* px = (Data*)malloc(sizeof(Data));
        //空间是申请出来了,该怎么调用构造函数呢?
        //px->_a = 1;//不可以,成员变量是私有的
        //px->Data(1);//不可以,构造函数只能由编译器调用(自动),不可以我们手动调用
        return 0;
    }

于是C++依旧建立了一套新的方案newdelete

1.2.C++的操作符new与delete

首先我们来学会使用newdelet,注意:newdelete是操作符,而不是函数。

1.2.1.new/delete操作内置类型

    #include 
    using namespace std;
    void Test()
    {
        //1.动态申请一个int类型的空间
        int* ptr1 = new int;
        //2.动态申请一个int类型的空间并初始化为10
        int* ptr2 = new int(10);
    
        // 动态申请10个int类型的空间
        int* ptr3 = new int[10];
        for (int i = 0; i < 10; i++)
        {
            ptr3[i] = i * i;
            cout << ptr3[i] << " ";
        }
    
        delete ptr1;
        delete ptr2;
        delete[] ptr3;//注意方括号必须带上
    }
    int main()
    {
        Test();
        return 0;
    }

单个元素使用newdelete操作符,多个元素使用new[]delete[]操作符。

另外C++98不支持进行初始化,但是C++11支持new使用()以及{}初始化,也就是C++11后可以这么写:

   #include 
    class Data
    {
        friend std::ostream& operator<<(std::ostream& out, Data& d);
    public:
        //1.构造函数
        Data(int data = 0)
            :_data(data)
        {
            //std::cout << "Data()" << std::endl;
        }
        //2.析构函数
        ~Data()
        {
            //std::cout << "~Data()" << std::endl;
        }
    private:
        int _data;
    };
    //2.友元函数:流重载函数
    std::ostream& operator<<(std::ostream& out, Data& d)
    {
        out << d._data;
        return out;
    }
    int main()
    {
    #define NUMEBER 10
        //1.new出单个内置类型
        //1.1.初始化方法1
        int* p1 = new int(1);
        std::cout << "p1 = " << p1 << " " << "*p1 = " << *p1 << std::endl;
        //1.2.初始化方法2
        int* p2 = new int{ 2 };
        std::cout << "p2 = " << p2 << " " << "*p2 = " << *p2 << std::endl;
        //1.3.初始化方法3
        int* p3 = new int{ (3) };
        std::cout << "p3 = " << p3 << " " << "*p3 = " << *p3 << std::endl;
        //2.new出内置类型数组
        //2.1.初始化方法1
        int* parr1 = new int[NUMEBER] { (1), (2), (3), (4), (5) };
        std::cout << "parr1 = " << parr1 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << parr1[i] << " ";
        }
        std::cout << std::endl;
        //2.2.初始化方法2
        int* parr2 = new int[NUMEBER] { { 1 }, { 2 }, { 3 }, { 4 }, { 5 } };
        std::cout << "parr2 = " << parr2 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << parr2[i] << " ";
        }
        std::cout << std::endl;
        //2.3.初始化方法3
        int* parr3 = new int[NUMEBER] { {(1)}, { (2) }, { (3) }, { (4) }, { (5) } };
        std::cout << "parr3 = " << parr3 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << parr3[i] << " ";
        }
        std::cout << std::endl;
        //2.4.初始化方法4
        int* parr4 = new int[NUMEBER] { 1, 2, 3, 4, 5 };
        std::cout << "parr4 = " << parr4 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << parr4[i] << " ";
        }
        std::cout << std::endl;
    
        std::cout << "------------" << std::endl;//内置类型和自定义类型的分隔符
    
        //3.new出单个自定义类型
        //3.1.初始化方法1
        Data* d1 = new Data(1);
        std::cout << "d1 = " << d1 << " " << "*d1 = " << *d1 << std::endl;
        //3.2.初始化方法2
        Data* d2 = new Data{ 2 };
        std::cout << "d2 = " << d2 << " " << "*d2 = " << *d2 << std::endl;
        //3.3.初始化方法3
        Data* d3 = new Data{ (3) };
        std::cout << "d3 = " << d3 << " " << "*d3 = " << *d3 << std::endl;
    
        //4.new出自定义类型数组
        //4.1.初始化方法1
        Data* darr1 = new Data[NUMEBER] { (1), (2), (3), (4), (5) };
        std::cout << "darr1 = " << darr1 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << darr1[i] << " ";
        }
        std::cout << std::endl;
        //4.2.初始化方法2
        Data* darr2 = new Data[NUMEBER] { { 1 }, { 2 }, { 3 }, { 4 }, { 5 } };
        std::cout << "darr2 = " << darr2 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << darr2[i] << " ";
        }
        std::cout << std::endl;
        //4.3.初始化方法3
        Data* darr3 = new Data[NUMEBER] { { (1) }, { (2) } , { (3) }, { (4) }, { (5) } };
        std::cout << "darr3 = " << darr3 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << darr3[i] << " ";
        }
        std::cout << std::endl;
        //4.4.初始化方法4
        Data* darr4 = new Data[NUMEBER] { 1, 2 , 3 , 4 , 5 };
        std::cout << "darr4 = " << darr4 << std::endl;
        for (int i = 0; i < NUMEBER; i++)
        {
            std::cout << darr4[i] << " ";
        }
        std::cout << std::endl;
    
        //5.释放资源
        delete p1;
        delete p2;
        delete p3;
        delete[] parr1;
        delete[] parr2;
        delete[] parr3;
        delete[] parr4;
        delete d1;
        delete d2;
        delete d3;
        delete[] darr1;
        delete[] darr2;
        delete[] darr3;
        delete[] darr4;
    
        return 0;
    }

C++不支持扩容 ,没有一个类似realloc的操作符。

使用newdelete更加适应自定义类型:

    #include 
    using namespace std;
    void Test()
    {
        //1.动态申请一个int类型的空间
        int* ptr1 = new int;
        //2.动态申请一个int类型的空间并初始化为10
        int* ptr2 = new int(10);
        //3.动态申请10个int类型的空间
        int* ptr3 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//如果没有写满会默认给0,哪怕是只有{}
        for (int i = 0; i < 10; i++)
        {
            cout << ptr3[i] << " ";
        }
    
        //4.释放空间
        delete ptr1;
        delete ptr2;
        delete[] ptr3;//注意方括号必须带上
    
    }
    int main()
    {
        Test();
        return 0;
    }
    
    class Data
    {
    public:
        Data(int x = 10)
        {
            _x = x;
        }
    private:
        int _x;
    };
    int main()
    {
        //Data* px = (Data*)malloc(sizeof(Data));
        //空间是申请出来了,该怎么调用构造函数呢?
        //px->_a = 1;//不可以,成员变量是私有的
        //px->Data(1);//不可以,构造函数只能由编译器调用(自动),不可以我们手动调用
        Data* px1 = new Data(1);//申请空间+自动调用构造函数初始化
        delete px1;//销毁空间+自动调用析构函数
    
        Data* px2 = new Data[10]{1, 2};//申请空间+自动调用构造函数初始化
        delete px2;//销毁空间+自动调用析构函数
    
        Data* px3 = new Data[10]{Data(1), Data(2)};//申请空间+自动调用构造函数初始化
        delete px3;//销毁空间+自动调用析构函数
    
        Data d1(1);
        Data d2(2);
        Data* px4 = new Data[10]{d1, d2};//申请空间+自动调用构造函数初始化
        delete px4;//销毁空间+自动调用析构函数
        return 0;
    }

利用C++new来写一个用来测试的链表简直不要太快:

    #include 
    using namespace std;
    class ListNode
    {
    public:
        ListNode(int val = 0)
            :_val(val)
            ,_next(nullptr)
        {}
        int _val;
        ListNode* _next;
    };
    int main()
    {
        ListNode* l1 = new ListNode(1);
        ListNode* l2 = new ListNode(2);
        ListNode* l3 = new ListNode(3);
        ListNode* l4 = new ListNode(4);
        ListNode* l5 = new ListNode(5);
        l1->_next = l2;
        l2->_next = l3;
        l3->_next = l4;
        l4->_next = l5;
    
        delete l1;
        delete l2;
        delete l3;
        delete l4;
        delete l5;
        return 0;
    }

1.2.2.new/delete操作自定义类型

对于内置类型使用这两个操作符来说,和mallocfree没有什么本质区别。但是在申请自定义类型大小的空间时,就有一些很大的变化了。

    #include 
    using namespace std;
    class Data
    {
    public:
        Data(int data = 0)
            :_data(data)
        {
            cout << "Data()" << endl;
        }
        ~Data()
        {
            cout << "~Data()" << endl;
        }
    private:
        int _data;
    };
    int main()
    {
        Data* dd1 = new Data;//不仅仅是开辟了空间还会调用类的成员函数(这里就调用了构造函数进行初始化)
        delete dd1;//释放空间(这里就调用了析构函数清理了对象中的资源)
    
        Data* dd2 = new Data[10];//不仅仅是开辟了空间还会调用类的成员函数(这里就调用了构造函数进行初始化)
        delete[] dd2;//释放空间(这里就调用了析构函数清理了对象中的资源)
        return 0;
    }

调用构造函数和析构函数就是与C语言最大的区别,因此实际上newdelete是为了自定义类型而准备的(这也就是为什么在C++98中一开始没有初始化,这是暗示在使用在自定义类型中需要通过写构造函数来进行初始化,C++98很有可能觉得没有必要添加初始化这一套)。一定要注意析构函数释放的是对象成员所使用的堆资源,而不是释放对象的空间。

1.2.3.new/delete匹配问题

  1. newdelet匹配,new[]delete[]匹配。

  2. new出来的空间不可以使用free()释放,malloc()出来的空间不可以使用delete释放。

1.3.new失败的解决方案

另外对于申请失败的情况,CC++也不一样。malloc()失败返回空指针,new失败抛异常。(抛出异常是面向对象语言出错的一种处理方式,相比与C语言的返回错误码更好一些)

C++处理堆错误的方式如下,有点类似go to的使用:

    #include 
    using namespace std;
    int main()
    {
        try
        {
            char* p1 = new char[0xffffffff];
            char* p2 = new char[0xffffffff];
            char* p3 = new char[0xffffffff];
            //如果您的电脑足够强大,可以尝试多加几句上述的代码,直到出现调用错误
            cout << "hello word" << endl;
        }
        catch(const exception& e)//如果new失败就会跳转到这里
        {
            cout << e.what() << endl;//并且这里是输出错误的理由
        }
        return 0;
    }

2.operator new()与operator delete()

newdelete是用户动态申请内存的释放内存的操作符,而operator new()operator delete()是系统提供的全局函数。

  1. new在底层调用operator new()来申请空间,然后在申请的空间上执行构造函数,完成对象的构造。

  2. delete在底层先执行析构函数,完成对象中资源的清理,调用operator delete()来释放空间。

注意:这里的operator只是函数名字的一部分,不是重载关键字,不要误会了。

您可以看一下newdelete调用的汇编代码,内部就调用了这两个全局函数。

2.1.operator new()的源码部分

可以观察到底层也是使用了malloc()函数开空间,并且处理了申请异常部分:

    /* 
    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);
    }

另外我们也可以像使用malloc()一样使用operator new(),但是失败还是抛出异常。

2.2.operator delete()的源码部分

可以观察到底层最终也是通过free()来释放空间的:

    /* 
    operator delete: 该函数最终是通过free来释放空间的
    */
    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;
    }
    /*free的实现*/ 
    #define free(p) _free_dbg(p, _NORMAL_BLOCK)

同样这个全局函数也是可以直接使用的。

吐槽:不过也有不少人吐槽这两个全局函数的名字,和运算符重载实在是很像……

2.3.使用operator new()与operator delete()

实际上这两个函数就是malloc()free()的封装(为了newdelete准备的)。因此我们直接使用也是可以的。


    #include 
    using namespace std;
    int main()
    {
        try
        {
            char* p = (char*)operator new(sizeof(char));
            operator delete(p);
        }
        catch (const exception& e)
        {
            cout << e.what() << endl;//并且这里是输出错误的理由
        }
        return 0;
    }

2.4.重载全局operator new()和operator delete()

在实际使用C++中,我们一般不直接使用这两个全局函数,包括malloc()free()都很少使用了。

但是有的时候有重载这两个全局函数的需求。

    #include 
    using namespace std;
    class Data
    {
    public:
        Data(int data)
            :_data(data)
        {
            cout << "Data()" << endl;
        }
        ~Data()
        {
            cout << "~Data()" << endl;
        }
    private:
        int _data;
    };
    void* operator new(size_t size, const char* fileName, const char* funcName, size_t lineNo)
    {
        void* p = ::operator new(size);
        cout << fileName << "-" << funcName << "-" << lineNo << "-" << p << "-" << size << endl;
        return p;
    }void operator delete(void* p, const char* fileName, const char* funcName, size_t lineNo)
    {
        cout << fileName << "-" << funcName << "-" << lineNo << "-" << p << endl;
        ::operator delete(p);
    }
    int main()
    {
        int* p1 = new(__FILE__, __FUNCTION__, __LINE__) int;
        operator delete(p1, __FILE__, __FUNCTION__, __LINE__);
    
    #ifdef _DEBUG
        #define new new(__FILE__, __FUNCTION__, __LINE__)
        #define delete(p) operator delete(p, __FILE__, __FUNCTION__, __LINE__)
    #endif
        int* p2 = new int;
        delete(p2);
        return 0;
    }

这样就可以方便以后定位内存泄露了(不过也挺少这么干的)。

2.5.重载局部operator new()和operator delete()

有时候频繁申请内存就会造成内存碎片,下面的代码等以后再回来看看就能明白了:

    #include 
    using namespace std;
    class ListNode
    {
    public:
        ListNode(int val)
            :_val(val),_next(nullptr)
        {}
        void* operator new(size_t n)
        {
            cout << "void* operator new(size_t n)->STL内存池allocator申请" << endl;
            //使用内存池和模板
            void* obj = alloc.allocate(1);
            return obj;
        }
        void operator delete(void* ptr)
        {
            cout << "void operator delete(void* ptr)->内存池deallocate释放" << endl;
            alloc.deallocate((ListNode*)ptr, 1);
        }
    private:
        int _val;
        ListNode* _next;
        static allocator<ListNode> alloc;
    };
    allocator<ListNode> ListNode::alloc;
    int main()
    {
        ListNode* node1 = new ListNode(1);
        ListNode* node2 = new ListNode(2);
        ListNode* node3 = new ListNode(3);
        delete node1;
        delete node2;
        delete node3;
    }

这样每个类都可以使用自己专属的new,这里了解一下就可以,以后再来认真学习。

3.operator new[] ()和operator delete[] ()

另外还有一个operator new[]()operator delete[](),这里就不再赘述了。

  1. new T[N]调用operator new[](),在operator new[]()中实际调用operator new()完成N个对象的申请,然后在申请的空间上执行N次构造函数

  2. delete[]在释放的对象空间上执行N次析构函数,完成资源清理,然后调用operator delete[]释放空间,实际在operator delete[]中调用operator delete()来释放空间。

因此这里有一个小点需要补充:

    #include 
    using namespace std;
    int main()
    {
        try
        {
            int* p = new int[10];
            //delete p;//方式1
            //free(p);//方式2
        }
        catch (const exception& e)
        {
            cout << e.what() << endl;//并且这里是输出错误的理由
        }
        return 0;
    }

上述方式1方式2是否会造成内存泄露呢?实际上是不会的,因为delete[]内部实际上调用的是operator delete[],这个函数内部调用的又是operator delete()因此对于内置类型来说,实际上是没有影响的。

真正会出现问题的是内置类型,因为内置类型会调用构造函数和析构函数,导致出现问题。

    //以下是VS2022下实验结果
    #include 
    using namespace std;
    class A
    {
    public:
        A(int a = 0)
            :_a(a)
        {
            cout << "A()" << endl;
        }
    private:
        int _a;
    };
    class B
    {
    public:
        B(int b = 0)
            :_b(b)
        {
            cout << "B()" << endl;
        }
        ~B()
        {
            cout << "~B()" << endl;
        }
    private:
        int _b;
    };
    int main()
    {
        B* p2 = new B[5];
        free(p2);//方式1
        delete p2;//方式2
        //这里p2在申请空间的时候,会多申请一部分空间(有可能是4字节)存储“5”来表示开辟的空间个数。因此申请的空间有部分是需要指针偏移来销毁的。
        //然后再调用delete[]的时候,就可以根据这个“5”来调用5次析构函数。
        //但是由于直接使用“free()”和“delete”就无法释放这部分用来表示使用多少次析构函数的空间。
        //因此真正的问题是指针错位的问题。  
        A* p1 = new A[5];
        free(p1);//方式1
        delete p1;//方式2
        //这里p2在申请空间的时候,会多申请一部分空间(有可能是4字节)存储“5”来表示开辟的空间个数。因此申请的空间有部分是需要指针偏移来销毁的。
        //然后再调用delete[]的时候,就可以根据这个“5”来调用5次析构函数。
        //由于这个类没有析构函数,所以有的编译器会很聪明,知道调用的是默认析构函数,就会直接返回指向标识空间的指针
        //因此直接返回的指针没有发生错位,不会出现很大的问题。
        return 0;
    }
    //但是换一个编译器极有可能都会奔溃,所以只要匹配使用即可……

还有的时候如果没有匹配,少调用了析构函数会直接造成内存泄漏。

3.1.定位new的使用

在我们定义出来的类中,我们可以发现,构造函数不可以被显式调用,而析构函数却可以(因为可以提前释放资源,而不必等系统自动调用)。

那么如果我们一定要显式使用构造函数呢?这就可以使用定位new来试试了。

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象,下面是定位new的使用格式。

    new (指向某空间的指针) type;
    new (指向某空间的指针) type(初始化列表);

在定位new中支持显式调用构造函数,使得开发者更加得自由:

    #include 
    using namespace std;
    class Data
    {
    public:
        Data(int a = 0, int b = 0, int* c = nullptr)
            :_a(a)
            ,_b(b)
        {
            cout << "Data(int a = 0, int b = 0, int c = 指针)" << " " << this << endl;
            c = new int;
            _c = c;
            *_c = 0;
        }
        ~Data()
        {
            cout << "~Data()" << this << endl;
            delete _c;
        }
    private:
        int _a;
        int _b;
        int* _c;
    };
    int main()
    {
        //1.使用new申请一块指针,指向一块对象空间
        Data* pdata1 = new Data;
        //2.使用malloc申请一块指针,指向一块对象空间
        Data* pdata2 = (Data*)malloc(sizeof(Data));
        if (!pdata2) exit(-1);
        //但是此时出现了一个问题没有办法代码初始化内部的成员,
        //在上述代码中,我们使用new运算符创建了Data类型的对象指针。
        //创建对象时,将自动调用Data的构造函数来完成对象的初始化。
        //我们不需要显式调用构造函数,编译器会为我们处理。
        //pdata1->Data(1, 2, 3);//非法
        //pdata2->Data(1, 2, 3);//非法
        //pdata1->~Data();//合法
        //pdata2->~Data();//合法
    
    
        //这个时候就可以使用定位new
        new(pdata1)Data(1, 2);//对已经分配的原始内存空间中调用构造函数初始化一个对象
        new(pdata2)Data(10, 20);//对已经分配的原始内存空间中调用构造函数初始化一个对象
    
        delete pdata1;//释放空间的同时自动调用析构函数
        pdata2->~Data();
        free(pdata2);//释放空间,但是内部资源需要我们手动调用析构函数
    
        return 0;
    }

或者这么使用:

    #include 
    using namespace std;
    class Data
    {
    public:
        Data(int a = 0, int b = 0, int* c = nullptr)
            :_a(a)
            ,_b(b)
        {
            cout << "Data(int a = 0, int b = 0, int c = 指针)" << " " << this << endl;
            c = new int;
            _c = c;
            *_c = 0;
        }
        ~Data()
        {
            cout << "~Data()" << " " << this << endl;
            delete _c;
        }
        void Print()
        {
            cout << _a << " " << _b << " " << (void*)_c << endl;
        }
    private:
        int _a;
        int _b;
        int* _c;
    };
    int main()
    {
        Data* d = (Data*)operator new(sizeof(Data));//不会自动调用析构函数
        d->Print();
        cout << endl;
        cout << "手动构造" << endl;
        new(d)Data(1, 2);
        d->Print();
        cout << endl;
    
        cout << "手动析构" << endl;
        d->~Data();
        cout << endl;
    
        return 0;
    }

甚至这里的内存地址指向的空间不一定是堆空间的,也可能是栈空间的。

相当于手动使用了new的过程,这一操作使得内存申请更加自由,也更容易产生内存泄露。

3.2.定位new的场景

有时也认为堆空间的效率不高(在频繁使用),因此可以提前在堆空间申请一个很大的空间,作为内存池管理起来,这个时候定位new就有很大的用处了。

因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定位表达式进行显示调构造函数进行初始化。不过由于我们没有学过内存池,所以我们以后再说……

具体来说,有几个作用:

  1. 自定义内存管理:使用定位new可以让我们在某个特定的内存位置上构造对象,这对于实现自定义的内存管理策略很有用。比如,如果我们想将对象放置在特定的内存池或缓冲区中,就可以使用定位new来构造对象。

  2. 特殊的内存需求:有时候我们可能需要在已经分配的特定内存块上构造对象,例如与硬件设备进行交互时需要使用特定的内存地址。

  3. 对象重用:定位new可以重新使用已经存在的对象内存,而无需重新分配新的内存。这在一些特殊场景下有效,例如对象池或内存池的实现。

4.两套动态内存管理的区别

在一些C++工程师的面试题目中,经常考察的几个区别就是:进程与线程的区别、malloc/freenew/deleta的区别、指针和引用的区别、TCPUDP的区别……

其中malloc/freenew/delete的区别可以从两个点入手:语法使用区别、本质功能区别、判空与异常

  1. 语法定义上:malloc()free()是函数,而newdelet是关键字。

  2. 语法使用上:malloc()申请空间时需要计算空间的大小并且传递,new只需要根据空间的类型,如果是多个对象则在[]中指定即可。

  3. 语法使用上:malooc()的返回值为void*,需要强制转化,而new不需要。

  4. 语法使用上:malloc()申请的空间无法初始化,而new申请的空间可以初始化。

  5. 本质功能:malloc()只会申请空间,不会进行初始化,new可以借助对象的构造函数初始化。free()只会释放空间,delet在释放对象空间的同时还会调用对象的析构函数,完成资源清理。

  6. 判空与异常:malloc()申请空间失败是返回NULL,因此需要判空。而new是抛出异常来被捕获,因此不需要判空。

共同的地方是:都是从堆上申请,并且都需要经过释放。

你可能感兴趣的:(C++学习笔记,c++,算法,开发语言)