【c++】动态内存管理

ops/2024/9/24 5:59:08/

🌟🌟作者主页:ephemerals__

🌟🌟所属专栏:C++

目录

前言

一、内存区域分布

二、c++中的动态内存管理方式

1. new与delete对内置类型的操作

2. new与delete对自定义类型的操作

三、operator new函数和operator delete函数

四、new和delete的实现原理

1. 内置类型

2. 自定义类型

五、定位new表达式

六、malloc/free和new/delete的区别总结

总结


前言

        之前在C语言当中,我们学习了动态内存管理的相关知识以及使用malloc/calloc/realloc/free函数实现堆区中动态内存的分配:

【c语言】动态内存管理-CSDN博客

而对于c++而言,我们有了新的方式来实现动态内存分配,它们就是newdelete。本篇文章我们就来详细探讨一下它们的使用方法以及c++/c语言实现动态内存管理的区别。

一、内存区域分布

        首先我们来看一段代码并尝试解决以下问题:

1. GlobalVar是全局变量,存储在数据段(静态区),选C。

2. staticGlobalVar是静态全局变量,也存储在数据段(静态区),选C。

3. staticVar是静态的局部变量,存储在数据段(静态区),选C。

4. localVar是局部变量,存储在栈区当中,选A。

5. num1是一个局部变量(存储着数组首元素的地址),存储在栈区中,选A。

6. char2与num1相同,是局部变量,选A。

    *char2表示字符数组中的字符‘a’,存储在栈区中,选A。

7. pchar3是指向常量字符串首字符的指针,是一个局部变量,选A。

    *pchar3表示常量字符串中的字符‘a’,属于常量,存储在代码段(常量区),选D。

8. ptr1是一个局部指针变量,存储在栈区,选A。

    *ptr1是动态开辟的内存区域中的值,存储于堆区中,选B。

这里重点关注一下*char2*pchar3的存储位置:char2所在语句的含义是将字符串"abcd"存储到字符数组当中,本质是存储到了变量当中,所以解引用之后得到的字符肯定是在栈区中存储的;而pchar3所在语句是将常量字符串"abcd"中首字符的地址存放于指针变量当中,该指针变量的值是存储在栈区当中的,但是解引用之后得到的字符是常量,所以*pchar3肯定存储于常量区

接下来,我们画图表示一下内存区域的分布

        c++的动态内存分配与c语言相同,也是在堆区中进行操作的。

二、c++中的动态内存管理方式

        之前在c语言当中,我们使用malloc/calloc/realloc/free函数来实现动态内存管理,但由于使用方式较为麻烦(例如要手动计算申请的内存大小、检查返回值等),所以c++引入了两个操作符,便于我们更高效地实现动态内存管理:new和delete

        接下来我们从代码角度来解释这两个关键字的使用方法。

1. new与delete对内置类型的操作

int main()
{int* p1 = new int;//动态申请一个int类型的空间int* p2 = new int(10);//动态申请一个int类型的空间,并初始化为10int* p3 = new int[10];//动态申请10个int类型的空间int* p4 = new int[10] {10};//动态申请10个int类型的空间,并将第一个元素初始化为10,其余元素为0//这里的初始化规则与数组定义时的初始化相同//释放内存delete p1;delete p2;delete[] p3;delete[] p4;return 0;
}

可以看到,我们使用new操作符申请内存时,不仅不用sizeof来计算申请所需的空间大小,而且还能对申请的空间进行初始化,十分方便。这里要注意:当我们释放连续的空间时,delete之后要加上“ [ ] ”

2. new与delete对自定义类型的操作

        new和delete申请和释放自定义类型的空间时,与内置类型语法是相同的。当我们使用new/delete操作自定义类型时,它们与malloc/free最大的区别是:new在申请内存空间之后还会调用构造函数对该空间进行初始化;delete会调用析构函数,然后释放内存空间。而malloc/free只会开辟空间,并不会调用这两种函数

代码示例:

#include <iostream>
using namespace std;class A
{
public:A(int a = 10, int c = 20):_a(a),_c(c){cout << "调用构造函数" << endl;}~A(){cout << "调用析构函数" << endl;}void Print() const{cout << _a << endl;cout << _c << endl;}
private:int _a;int _c;
};int main()
{A* p = new A{ 3,5 };//动态申请一个A类型的空间,并且调用构造函数初始化p->Print();//打印内容delete p;//调用析构函数,然后释放空间return 0;
}

运行结果:

三、operator new函数和operator delete函数

        operator new函数和operator delete函数是c++提供的全局函数,当我们使用new或者delete操作符时,它们就会调用这两个函数来实现相关功能。以下是这两个函数的底层实现

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* 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);
}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_FINALLYreturn;
}

可以看到,在operator new函数体当中,首先调用了malloc函数来申请内存空间,如果申请成功则直接返回,否则就会启动相关应对措施;在operator delete函数体中,我们可以看到一个函数“_free_dbg”,它其实就是free的底层实现。所以说operator new使用了malloc来开辟内存,operator delete使用了free来释放内存,只不过在其中添加了一些异常处理机制等,使得内存开辟更加完善

        了解了这两个函数的运行机制,我们由此总结出newdelete的实现原理:

四、new和delete的实现原理

1. 内置类型

        对于内置类型而言,new/delete与malloc/free基本类似,不同的地方是:在申请空间时可以进行初始化,并且new在空间申请失败时会抛出异常,而malloc会返回空指针

2. 自定义类型

        对于自定义类型,它的实现逻辑就比较复杂了,我们逐一分析:

1. new:首先调用operator new函数申请内存空间,然后调用构造函数,完成初始化

2. delete:首先调用析构函数,对开辟的内存进行资源清理,然后调用operator delete函数释放内存

3. new[]:首先调用 operator new[ ] 函数申请多个对象的内存空间(该函数中调用了operator new),然后调用N次构造函数,完成初始化

4.delete[]:首先调用N次析构函数清理资源,然后调用 operator delete[ ] 函数释放空间(该函数中调用了operator delete)

五、定位new表达式

        我们都知道,当对象被创建的时候,会自动调用构造函数。那么我们能否在一块已有的内存区域上显示调用构造函数构造对象呢?语法上是不允许显示调用构造函数的,但是定位new表达式可以做到:

class A
{
public:A(int a = 10, int c = 20):_a(a),_c(c){cout << "调用构造函数" << endl;}~A(){cout << "调用析构函数" << endl;}void Print() const{cout << _a << endl;cout << _c << endl;}int _a;int _c;
};int main()
{A* p = (A*)malloc(sizeof(A));//申请内存空间,但不调用构造函数new(p)A();//使用定位new表达式调用构造函数p->Print();//打印一下成员p->~A();//显示调用析构函数return 0;
}

运行结果:

可以看到,我们成功使用定位new表达式调用了构造函数并且为成员变量设置初始值。定位new表达式在实现内存池或缓存区等高级内存管理策略时非常有用。定位new表达式的语法是:

new(ptr)Class(参数)

这里的ptr表示指向该内存区域的指针,Class是类名。当构造函数中有非缺省参数时,需要我们在类名之后的括号中传参。

定位new表达式的注意事项:

1. 常规new表达式既负责分配内存,还负责构造对象;而定位new表达式只负责构造对象。所以在使用定位new表达式之前,要确保以及分配好足够的内存。

2. 使用定位new表达式调用构造函数后,如果我们不再使用该对象,要记得主动调用其析构函数并释放内存。

六、malloc/free和new/delete的区别总结

        最后,我们总结一下malloc/freenew/delete的区别:

共同点:都是从堆区申请空间,并且使用结束后要进行手动释放。

不同点:

1. malloc和free是函数,而new和delete是操作符。

2. malloc申请的空间不会进行初始化,而new可以初始化。

3. malloc申请空间时,需要手动计算申请的空间大小;而new申请时只需要说明类型与个数即可。

4. malloc申请空间返回的指针是void* 类型,需要进行强转,而new不需要。

5. malloc申请失败会返回NULL,所以使用时必须检查返回值;而new申请失败会抛出异常,无需检查返回值。

6. 对于自定义类型空间的开辟,malloc和free只会开辟/销毁对应的内存空间,而new和delete会调用构造函数/析构函数完成初始化/资源清理操作。

总结

        今天我们学习了C++中动态内存管理的方法、它们的实现原理以及它们与c语言内存管理方法的区别。我们能够感觉到,相比c语言,C++动态开辟内存的方法更加方便。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤


http://www.ppmy.cn/ops/115142.html

相关文章

基于51单片机的矿井安全检测系统

基于51单片机的矿井安全检测系统使用51单片机作为系统主控&#xff0c;LCD1602进行显示同时系统集成了ADC0808和烟雾传感器、甲烷传感器&#xff0c;二者结合测量环境烟雾值&#xff0c;同时使用DHT11温湿度传感器获取环境温湿度值&#xff0c;使用L298N驱动风扇&#xff0c;利…

Gin渲染

HTML渲染 【示例1】 首先定义一个存放模板文件的 templates文件夹&#xff0c;然后在其内部按照业务分别定义一个 posts 文件夹和一个 users 文件夹。 posts/index.tmpl {{define "posts/index.tmpl"}} <!DOCTYPE html> <html lang"en">&…

WEB攻防- Oracle基本注入

前置知识 1.dual表 此表是Oracle数据库中的一个自带表&#xff0c;为满足查询条件而产生。与MySQL不同的是&#xff0c;在MySQL中查询语句可以直接是&#xff1a;select 1,2&#xff0c;但是在Oracle中就必须跟一个表名&#xff0c;但是如查询日期是没有表的&#xff0c;就可以…

线性代数(宋浩版)(4)

2.4逆矩阵 &#xff08;不要把矩阵放在分母上&#xff09; 方阵的行列式 性质1 性质2 性质3 伴随矩阵&#xff08;只有方阵才有&#xff09; 1.求出所有元素的代数余子式&#xff08;矩阵先求行列式&#xff09;。 2.按行求的代数余子式按列放。 定理1&#xff08;重要&…

【C++指南】C++中nullptr的深入解析

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 目录 引言 一、nullptr的引入背景 二、nullptr的特点 1.类型安全 2.明确的空指针表示 3.函数重载支…

Redis性能测试redis-benchmark

Redis性能测试redis-benchmark redis-benchmark 是 Redis 自带的性能测试工具,主要用于评估 Redis 在不同场景下的性能。以下是使用 redis-benchmark 的一些基本步骤和参数说明: 基本用法 启动测试: 在命令行中运行以下命令: redis-benchmark这将运行一系列默认的测试。 …

【设计模式-适配】

Adapter Pattern&#xff08;适配器模式&#xff09; 是一种结构型设计模式&#xff0c;其主要目的是让不兼容的接口能够协同工作。适配器模式通过引入一个适配器类&#xff0c;转换一个类的接口&#xff0c;使得原本不兼容的接口可以互相配合&#xff0c;从而实现接口的兼容性…

【Android Studio】app:compileDebugJavaWithJavac FAILED解决办法

文章目录 问题描述解决办法 问题描述 Task :app:compileDebugJavaWithJavac FAILED The following annotation processors are not incremental: jetified-butterknife-compiler-10.0.0.jar (com.jakewharton:butterknife-compiler:10.0.0). Make sure all annotation processo…