0


C++内存管理

**这篇文章我们来谈谈C++如何进行对内存的管理 **


new、delete关键字

int main()
{
    //基本用法
    int* p1 = new int;
    delete p1;
    
    //内存申请且完成初始化
    int* p2 = new int(0);
       delete p2;
    
    //开辟多个空间:
    int* p3 = new int[10];
    delete[] p3;
    
    //开辟多个空间且完成初始化:
    int* p4 = new int[10]{1,2,3,4}; //初始化先4个,后面的默认初始化为0
       delete[] p4;
    
    //对于内置类型来说,相比于C语言中的malloc/free,除了用法区别之外没有其他区别
    return 0;
}
//真正的区别在于自定义类型上:
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A()" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

int main()
{
    A* p1 = new A;
    A* p2 = (A*)malloc(sizeof(A));
    delete p1;
    free(p2);
    return 0;

    //new和delete 相比于malloc和free,除了空间管理,还会调用构造函数和析构函数
}

✨ **可以清楚地看出:new和delete分别调用了构造函数和析构函数,这就是和malloc和free区别的地方 **

//对于链表而言:
struct ListNode
{
    int _val;
    ListNode* _next;
    
    ListNode(int val = 0)
        :_next(nullptr)
        , _val(val)
    {}
};

int main()
{
    //我们可以使用new能够调用析构函数的特性轻松地初始化
    ListNode* n1 = new ListNode(1);
    ListNode* n2 = new ListNode(2);
    ListNode* n3 = new ListNode(3);
    ListNode* n4 = new ListNode(4);
    n1->_next = n2;
    n2->_next = n3;
    n3->_next = n4;
    return 0;
}

new和delete的匹配问题

//思考:
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A()" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

int main()
{
    //正常匹配:
    //对于单个对象匹配形式
    A* p1 = new A;
    delete p1;

    //对于多个对象匹配形式
    A* p2 = new A[10];
    delete[] p2;

    //非正常匹配:多对单
    A* p3 = new A[10];
    delete p3; //在vs平台中会发生什么? ——如下图所示:
}

这种场景在vs平台中会报错,这依赖于编译器的底层实现,换一个编译器可能报错也可能不报错,这里大致说一下vs的机制如下图:

**当然,delete不知道要删除多少个对象,实际上是不知道要调用多少次析构函数,若是在类中没有显示写析构函数,编译器则无需考虑调用多少次析构函数,编译器也很智能,故而不会开辟额外空间,所以这时候不管是delete[] p1,还是delete p1都是p1处开始释放,编译器不会报错。 **


new和malloc深层次关系

对于使用malloc来说,当我们在当前程序下不断申请内存最终一定会失败,失败的时候会返回空指针,这时候我们只需要检验指针是否为空即可

而相同情景下使用new来申请空间,失败并不会走进else,而是会抛异常,所以用new并不需要检查返回值

//这里了解即可
int main()
{
    try
    {
        while (1)
        {
            int* p1 = new int[1024 * 1024];
            if (p1)
            {
                cout << p1 << endl;
            }
            //使用new申请内存失败并不会跳入else,而是直接进入catch
            else
            {
                cout << "malloc申请失败!" << endl;
                break;
            }
        }
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
}

new的底层机制:

调用operator new (并不是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);
}
/*
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;
}
//通过一个例子来看看细节
struct ListNode
{
    int _val;
    ListNode* _next;
    
    ListNode(int val = 0)
        :_next(nullptr)
        , _val(val)
    {}

};
int main()
{
    //这里转到反汇编来进一步查看
    ListNode* p1 = new ListNode;
    
    return 0;
}

这里可以主要看到调用两个函数,一个是operator new,还有一个是ListNode的构造函数,印证了之前所说的。至于为什么要封装malloc则是符合C++面向对象处理错误抛出异常的方式(异常的优势之后的文章再谈)

//我们也可以手动调用operator new,使用方法和malloc一致,唯一的区别就是失败了不返回空而是抛异常
char* p1 = (char*)operator new(1024*1024);

定位new表达式(placement-new)(了解)

**使用场景: **


定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化(了解即可)


**果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。 **

(如果是malloc出来的对象,其并未被初始化,就可以使用定位new来初始化)

class A
{
public:
     A(int a = 0)
     : _a(a)
     {
         cout << "A():" << this << endl;
     }
     ~A()
     {
     cout << "~A():" << this << endl;
     }
private:
     int _a;
};

int main()
{
 
  A* p1 = (A*)malloc(sizeof(A));
  //定位new的使用方法如下:
  new(p1)A(1);  //new(地址)类(初始化列表),这里如果A在构造函数中没给定缺省值则需要传参
  p1->~A();
  free(p1);
  return 0;
}

✨** 有时候我们并不想使用new的抛出异常特性就可以使用malloc+定位new来替代**


内存泄露

抛出一个简单的问题:内存泄漏是指针丢失还是内存丢失?

——严格意义上说是指针丢失,因为内存还是在特定位置,只不过是原本维护该空间的指针现在找不到该空间了

✨**什内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 **

**存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 **

**该段内存的控制,因而造成了内存的浪费。 **

**✨内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 **

内存泄漏会导致响应越来越慢,最终卡死

void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里调用Func函数的时候发生异常,无法调用之后的delete[] p3,导致内存泄露
  
  delete[] p3;
}

C++中一般我们关心两种方面的内存泄漏:

✨**堆内存泄漏(Heap leak) **

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一

块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分

内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

✨**系统资源泄漏 **

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放

掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。


总结

关于malloc/free && new/delete的区别:

*** ✨malloc和free是函数,new和delete是操作符 ***

*** ✨malloc申请的空间不会初始化,new可以初始化 ***

*** ✨ malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, ***

***如果是多个对象,[]中指定对象个数即可 ***

*** ✨ malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型 ***

*** ✨ malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需 ***

***要捕获异常 ***

*** ✨申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new ***

***在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成 ***

空间中资源的清理


标签: c++ 数据结构 链表

本文转载自: https://blog.csdn.net/Lin_zhiyouyu/article/details/127389129
版权归原作者 林知有雨 所有, 如有侵权,请联系我们删除。

“C++内存管理”的评论:

还没有评论