Cpp-Dynamic_Memory_Management

Dynamic_Memory_Management


什么是动态内存管理

C / C++ 拥有多种数据类型,不同的数据类型将被分配在不同的内存区域:

例如 int,指针类型数据被存放在栈区;static 和 全局数据存放在数据段。而动态内存开辟的空间存放在堆区。在 C 语言中,用于动态内存管理的库函数有:malloc calloc realloc free,而在 C++ 中,将引入:new delete new[] delete[]。具体原因是 C++ 引入了类和对象,仅用 C 语言的动态内存管理无法和类和对象完美契合,故有了它们的产生。


new & delete - 内置类型

new

当需要动态开辟内置类型时,new 的使用方法为:

cpp 复制代码
type* p = new type;

不需要进行强制类型转换,因为在开辟空间时已经指定了类型,利用模板即可返回对应类型,这意味着用 auto 接收也是可以的。

在开辟的同时可以进行初始化:

cpp 复制代码
int* p = new int(521);

内置类型初始化的方式为:(val)

delete

动态开辟的空间需要释放,而 delete 正是起到这一作用:

cpp 复制代码
delete name;

new[]

用 new 同时也可以开辟数组类型的空间:

cpp 复制代码
type* parr = new type[size];

开辟数组类型同时也支持初始化:

cpp 复制代码
int* parr = new int[1024]{1,2,3};

内置类型的初始化的方式为:{p1, p2, p3}

delete[]

开辟的数组空间需要用 delete[] 释放:

cpp 复制代码
delete[] name;

可见对于内置类型而言,其与 C 语言的动态开辟并无太大区别,可以进行开辟时初始化,开辟方式更为简洁值得称赞。不过在 C 语言中,动态内存开辟使用的是函数,而 C++ 中则是操作符,这一点需要注意。更重要的是它们在自定义类型中的特性。


new & delete - 自定义类型

以下内容基于此类讲解:

cpp 复制代码
class Thepale
{
private:
    int _data1;
    int _data2;

public:
    Thepale(int data1 = 0, int data2 = 0)
        :_data1(data1)
        ,_data2(data2)
    {
        cout << "构造函数被调用" << endl;
    }

    Thepale(const Thepale& e)
    {
        _data1 = e._data1;
        _data2 = e._data2;
        cout << "拷贝构造函数被调用" << endl;
    }

    ~Thepale()
    {
        cout << "析构函数被调用" << endl;
    }
};

可以通过 new 创建对象:

cpp 复制代码
int main()
{
    Thepale* p = new Thepale;
    
    return 0;
}

程序的输出结果为:

cpp 复制代码
构造函数被调用

可以发现 new 创建对象会调用默认构造函数

可以通过 delete 销毁对象:

cpp 复制代码
int main()
{
    Thepale* p = new Thepale;
    delete p;
    
    return 0;
}

程序的输出结果为:

cpp 复制代码
构造函数被调用
析构函数被调用

可以发现 delete 销毁对象会调用析构函数

在通过 new 创建对象时仍然可以进行传值构造对象:

cpp 复制代码
int main()
{
    Thepale* p = new Thepale(521, 1314);
    delete p;
    
    return 0;
}

可以使用 new[] 构建对象数组并初始化,并使用 delete[] 销毁对象数组:

cpp 复制代码
int main()
{
    Thepale* parr = new Thepale[10]{ {1, 2}, {3, 4} }; //若写成{(1, 2), (3, 4)} 小括号会被当做表达式
    delete[]  parr;
    return 0;
}
//--------------------------------------------------------------或者
int main()
{
    Thepale* parr = new Thepale[10]{ Thepale(1, 2), Thepale(3, 4) };
    delete[]  parr;
    return 0;
}

以上无论哪种方式在编译器优化后都是一次拷贝构造。


底层探究

用以下代码进行分析:

cpp 复制代码
int main()
{
    Thepale* p = new Thepale;
    delete p;

    return 0;
}

new 对应的汇编:

assembly 复制代码
    Thepale* p = new Thepale;
00007FF6C62623AB  mov         ecx,8  
;00007FF6C62623B0  call        operator new (07FF6C626104Bh)  
00007FF6C62623B5  mov         qword ptr [rbp+108h],rax  
00007FF6C62623BC  cmp         qword ptr [rbp+108h],0  
00007FF6C62623C4  je          main+50h (07FF6C62623E0h)  
00007FF6C62623C6  xor         r8d,r8d  
00007FF6C62623C9  xor         edx,edx  
00007FF6C62623CB  mov         rcx,qword ptr [rbp+108h]  
;00007FF6C62623D2  call        Thepale::Thepale (07FF6C62610C3h)  
00007FF6C62623D7  mov         qword ptr [rbp+138h],rax  
00007FF6C62623DE  jmp         main+5Bh (07FF6C62623EBh)  
00007FF6C62623E0  mov         qword ptr [rbp+138h],0  
00007FF6C62623EB  mov         rax,qword ptr [rbp+138h]  
00007FF6C62623F2  mov         qword ptr [rbp+0E8h],rax  
00007FF6C62623F9  mov         rax,qword ptr [rbp+0E8h]  
00007FF6C6262400  mov         qword ptr [p],rax

已用注释标出,发现 new 先调用了 operator new,然后调用构造函数

delete 对应的汇编:

assembly 复制代码
    delete p;
00007FF6C6262404  mov         rax,qword ptr [p]  
00007FF6C6262408  mov         qword ptr [rbp+128h],rax  
00007FF6C626240F  cmp         qword ptr [rbp+128h],0  
00007FF6C6262417  je          main+0A3h (07FF6C6262433h)  
00007FF6C6262419  mov         edx,1  
00007FF6C626241E  mov         rcx,qword ptr [rbp+128h]  
;00007FF6C6262425  call        Thepale::`scalar deleting destructor' (07FF6C626153Ch)  这一层是封装,进入查看
00007FF6C626242A  mov         qword ptr [rbp+138h],rax  
00007FF6C6262431  jmp         main+0AEh (07FF6C626243Eh)  
00007FF6C6262433  mov         qword ptr [rbp+138h],0 

ForCppTest.exe!Thepale::`scalar deleting destructor'(unsigned int):
00007FF6C62624F0  mov         dword ptr [rsp+10h],edx  
00007FF6C62624F4  mov         qword ptr [rsp+8],rcx  
00007FF6C62624F9  push        rbp  
00007FF6C62624FA  push        rdi  
00007FF6C62624FB  sub         rsp,0E8h  
00007FF6C6262502  lea         rbp,[rsp+20h]  
00007FF6C6262507  mov         rcx,qword ptr [this]  
;00007FF6C626250E  call        Thepale::~Thepale (07FF6C62613C5h)  
00007FF6C6262513  mov         eax,dword ptr [rbp+0E8h]  
00007FF6C6262519  and         eax,1  
00007FF6C626251C  test        eax,eax  
00007FF6C626251E  je          Thepale::`scalar deleting destructor'+41h (07FF6C6262531h)  
00007FF6C6262520  mov         edx,8  
00007FF6C6262525  mov         rcx,qword ptr [this]  
;00007FF6C626252C  call        operator delete (07FF6C6261401h)  
00007FF6C6262531  mov         rax,qword ptr [this]  
00007FF6C6262538  lea         rsp,[rbp+0C8h]  
00007FF6C626253F  pop         rdi  
00007FF6C6262540  pop         rbp  
00007FF6C6262541  ret  

已用注释标出,发现 delete 先调用了析构函数,然后调用 operator delete

关于 operator new 和 operator delete 在较新的编译器中已找不到相关定义代码,故进行了部分借鉴和参考:

cpp 复制代码
//operator new
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)
     {
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
    
	return (p);
}

operator new 尝试使用 malloc 开空间,若开辟失败尝试执行空间不足的应对措施,如果再次失败则抛异常。

cpp 复制代码
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;
}

#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

释放空间则是通过 free 来完成的。

简言之,new 和 delete 将被拆分为以下部分:

若是 new[] 和 delete[] 也就是依次重复以上步骤。调用顺序是从左至右的(地址从低到高)。


Placement New

定位 new 是一种调用构造函数的方式,若对象被创建但没有调用构造函数初始化则可以借助定位 new 调用构造函数(在内存池中会应用到)。

cpp 复制代码
class Thepale
{
private:
    int _data1;
    int _data2;

public:
    Thepale(int data1 = 0, int data2 = 0)
        :_data1(data1)
        , _data2(data2)
    {
        cout << "构造函数被调用"<< data1 << endl;
    }

    Thepale(const Thepale& e)
    {
        _data1 = e._data1;
        _data2 = e._data2;
        cout << "拷贝构造函数被调用" << endl;
    }

    ~Thepale()
    {              
        cout << "析构函数被调用" << endl;
    }
};


int main()
{
    Thepale* e = (Thepale*)malloc(sizeof(Thepale));
    new(e)Thepale(1, 1); //主动调用构造函数

    e->~Thepale(); //主动调用析构函数

    return 0;
}
相关推荐
myloveasuka9 分钟前
类与对象(1)
开发语言·c++
酸奶代码9 分钟前
Spring AOP技术
java·后端·spring
代码小鑫20 分钟前
A034-基于Spring Boot的供应商管理系统的设计与实现
java·开发语言·spring boot·后端·spring·毕业设计
paopaokaka_luck27 分钟前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
程序猿麦小七1 小时前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~1 小时前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong1 小时前
SpringBoot后端解决跨域问题
spring boot·后端·python
ROC_bird..1 小时前
STL - vector的使用和模拟实现
开发语言·c++
机器视觉知识推荐、就业指导1 小时前
C++中的栈(Stack)和堆(Heap)
c++
.生产的驴1 小时前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq