1 C++内存分布

        1.又叫堆,栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

        2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)

        3. 用于程序运行时动态内存分配,堆是可以上增长的。

        4. 数据段--存储全局数据和静态数据。

        5. 代码段--可执行的代码/只读常量。

        

2 C++内存管理方式

        C语言的内存管理虽然在C++中仍然适用,但是在某些情况下,C语言的内存管理方式使用是有不方便的地方,比如,初始化类,所以,C++提出了它自己独特的内存管理方式。

new和delete

1 使用方法

        1. 变量=new type(初始化) ,delete 变量

        2. 变量=new type[开辟空间个数] {初始化内容},delete[]  变量

2 C++与C内存管理的区别

        1.C++的内存管理的new和delete是关键字而C语言的内存管理malloc,realloc,calloc和free都是函数

        2.C++的内存管理在开辟类的空间和释放类的空间的时候会调用类的构造函数和析构函数而C语言的内存管理函数只开辟空间

3 开辟空间失败处理

        C语言开辟空间失败时,则其开辟空间的函数返回值是NULL空指针,而在C++中它并不是反回空指针而是抛异常而我们只要使用try catch来捕获异常即可不会导致整个程序崩溃便于我们调试

例:

#include <iostream>
using namespace std;

int main()
{
    try
    {
        int a =new int[10] {1,2,7,6,4,4}//没有初始化的默认为0
    }catch(const except& ect)
    {
        cout<<ect.what()<<endl;//输出错误信息方便纠错
    }
    return 0;
}

4 new和delete在开辟内置类型和自定义类型空间的实例

#include <iostream>
using namespace std;

class A
{
public:
    A(int n=1)
       :_aa(n)
    {

    }
    ~A()
    {
        _aa=0;
    }
private:
    int _aa=1;
};;

int main()
{
    //开辟内置类型空间
    int a1=new int(1);//(初始化)
    int* a2=new int[10] {1,2,3,4,5,6,7,8,9,10};//[开辟空间个数] {初始化}
    delete a1;
    delete a2;//开辟多个空间释放要用delete[]


    //开辟自定义类型空间
    A  a2 =new A(1)//(初始化),隐类型转化构造详情请看其他文章
    A*  a3 =new A[3] {1,2,3}//自定义类型的开辟多空间初始化
    return 0;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。


3 C++中new和delete的底层原理

1 operator new与operator delete函数

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

operator new在库中的实现原理:

/
*
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在库中的实现原理:

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

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

2 实现原理

 1 内置类型  

        如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是: new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

2 自定义类型

new的原理

1. 调用operator new函数申请空间

2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

1. 在空间上执行析构函数,完成对象中资源的清理工作

2. 调用operator delete函数释放对象的空间

new T[N]的原理

1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

2. 在申请的空间上执行N次构造函数

delete[]的原理

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

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

注意:new是先开辟空间再调用构造函数,delete是先调用析构函数再销毁空间

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

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

使用格式:

new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

例:

#include <iostream>
using namespace std;

class A
{
public:
    A(int n=1)
       :_aa(n)
    {

    }
    ~A()
    {
        _aa=0;
    }
private:
    int _aa=1;
};

int main()
{
    A * a =(A*)malloc(sizeof(A));//此时开辟空间但是没有初始化就可以用new重定位
    new(a)A(2);//用new重定位并且初始化
    return 0;
}

5 C++与C语言混用和C++内存管理使用不规范发生的问题

1 先搞懂底层原理(为什么不能混用)、

1. new[] / delete[] 做了什么

  • new[]
    1. 整个数组分配内存;
    2. 为数组中每一个对象调用构造函数
    3. 额外存储数组长度(用于后续批量析构)。
  • delete[]
    1. 为数组中每一个对象调用析构函数
    2. 释放整个数组的内存。

2. new / delete 做了什么

  • new:分配单个对象内存 + 调用一次构造函数;
  • delete:调用一次析构函数 + 释放单个对象内存。

2 C语言与C++内存管理函数混用

        如果开辟空间是内置类型则没有什么关系,但是如果是自定义类型并且这个类里开辟了空间,则在使用时不会调用构造函数与析构函数可能会导致内存泄露等问题

3  C++内存管理使用不匹配

1 new 与delete[ ]

        delete[ ]会通过数组前头多开的空间中记录的长度来释放空间。如果是内置类型则程序可以正常运行,但是如果是外置类型会调用多次析构函数会对空间多次释放会报错

2 new[ ]与delete

        new[ ]在开辟空间时,会而外多开一个空间记录开辟空间元素个数,但是delete也可以正常释放空间,但是如果没有定义析构函数则不会多开字节存储数据个数所以这用delete释放空间会出错既会错误计算内存地址,释放非法内存,还有如果是自定义类型且自定义类型内部有开空间则容易导致内存泄漏

6 C++中的拷贝问题
 

1 为什么会有拷贝问题?

        我们以string的模拟实现中的拷贝构造举例

看上图,如果用系统默认生成的默认拷贝构造函数的话,则s1和s2会指向同一块内存空间,所以当test函数结束的时候s1和s2对象会对同一块空间析构两次,会造成析构访问。由此拷贝问题就产生了两种拷贝类型深拷贝和浅拷贝


2 浅拷贝

        浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果在对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。


        就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏
        可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了

3 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给
出。一般情况都是按照深拷贝方式提供。

由上图可知,深拷贝就是给每个对象分配一个资源空间然后在将被拷贝对象的资源拷贝到拷贝对象资源空间里这样每一个对象都有单独的资源空间互相不影响


4 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的
引用计数:用来记录资源使用者的个数(一般是单独开一个空间然后在将指向这个空间的指针存在类里用来存放使用者个数的数据)。在构造时,将资源的计数给成1,每增加一个对象使用该
资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源
ps:引用计数在后面智能指针的章节会详细解释和使用
 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐