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;
}
相关推荐
程序员cxuan15 分钟前
Claude Code 为了封禁中国用户,竟然在代码里下毒
人工智能·后端·程序员
郡杰22 分钟前
Git基础与开发平台搭建
git·后端
ServBay38 分钟前
MCP (Model Context Protocol) 深度指南,协议原理、Python 实现与生产级安全实践
后端·ai编程·mcp
道友可好1 小时前
Claude Code 泄露源码里的 89 个秘密
前端·人工智能·后端
用户8356290780512 小时前
使用 Python 保护和取消保护 Excel 工作表和工作簿
后端·python
王老师青少年编程2 小时前
2026年6月GESP真题及题解(C++二级):完全平方数计数
c++·题解·真题·gesp·二级·2026年6月·完全平方数计数
zwenqiyu2 小时前
非线性字符串数据结构串讲
数据结构·c++·学习·算法
Java面试题总结2 小时前
CubeSandbox 实战:从零部署到快照/克隆/回滚全体验
后端
Csvn2 小时前
Python 开发技巧:函数式编程与 lambda 进阶
后端
考虑考虑2 小时前
nginx配置ssl
运维·后端·nginx