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;
}
相关推荐
小码农<^_^>4 分钟前
c++继承(下)
开发语言·c++
盒马盒马19 分钟前
Redis:cpp.redis++通用接口
数据库·c++·redis
努力的布布22 分钟前
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
java·后端·spring
PacosonSWJTU27 分钟前
spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理)
java·后端·springmvc
记得开心一点嘛36 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
黄俊懿1 小时前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构
无夜_1 小时前
Prototype(原型模式)
开发语言·c++
2401_857439692 小时前
“衣依”服装销售平台:Spring Boot技术应用与优化
spring boot·后端·mfc
刘好念2 小时前
[图形学]smallpt代码详解(1)
c++·计算机图形学
fpcc2 小时前
并行编程实战——TBB框架的应用之一Supra的基础
c++·并行编程