当前位置: 首页 > news >正文

C++——内存管理

目录

引言

C/C++的内存分布

C语言中动态内存管理方式

C++内存管理方式

1.new/delete操作内置类型

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

operator new与operator delete函数

new与delete的实现

1.内置类型

2.自定义类型

定位new表达式

malloc/free和new/delete的区别

结束语


引言

在简单的学习完类与对象之后,我们接下来学习C++的内存管理。

求点赞收藏评论关注!!!

C/C++的内存分布

我们先来看一段代码以及问题:

int globalVar = 1;
static int staticGlobalVar = 1;void Test()
{static int staticVar = 1;int localVar = 1;int num1[10] = { 1, 2, 3, 4 };char char2[] = "abcd";const char* pChar3 = "abcd";int* ptr1 = (int*)malloc(sizeof(int) * 4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}

选择题:

问题:以上数据存放在什么位置?

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

1.globalVar 在哪里?                        C
2.staticGlobalVar 在哪里?              C
3. staticVar 在哪里?                        C
4. localVar 在哪里?                         A
5.num1 在哪里?                             A  
6.char2 在哪里?                             A
7.*char2 在哪里?                            A
8. pChar3在哪里?                          A
9.*pChar3 在哪里?                        D
10.ptr1 在哪里?                             A
11.*ptr1 在哪里?                            B

解析:

1.globalVar 是全局变量,全局变量放在静态区。 C

2.staticGlobalVar 也是全局静态变量,放在静态区。与globalVar 的区别是: 普通全局变量作用于整个代码,可被其他文件访问或修改。staticGlobalVar 被 static 修饰,被 static 修饰的静态全局变量只作用于当前文件,其他文件不可见。 C

3.staticVar 是局部静态变量,局部静态变量虽然访问作用域在函数中,但它与全局变量一样,存放在静态区。 C

4.localVar 是局部变量,局部变量放在栈。被static修饰的局部变量的生命周期只会在程序结束后结束,而普通的局部变量的生命周期出了当前作用域就会结束。 A

5.num1 是数组名,是一个局部变量,放在栈区。 A

6.char2 也是一个局部变量,放在栈区,常量字符串"abcd"放在代码段(常量区),数组开辟的空间放在栈区。 A

7.*char2 这里表示的是数组首元素的地址,解引用即表示第一个元素 ‘a’。虽然我们知道常量字符串是存储在常量区,但是,数组中存的 “abcd” 是从常量区中 拷贝 过来存储在数组中,依然是在栈上。 A

8.pChar3 是指针变量,是局部变量,是在栈上开辟空间存储 “abcd” 首字符的地址的变量。 A

9.*pChar3 指向 “abcd” 首元素的地址 ‘a’。“abcd” 是只读常量,放在常量区。 D

10.ptr1 与 pChar3 同理,都是存储在栈上。 A

11.ptr1 指向动态开辟的空间,而 *ptr1 则是问动态开辟出的空间存储在哪,存储在堆上。 B

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

2.内存映射段是高效的I/0映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

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

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

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

C语言中动态内存管理方式

C语言中有关动态内存管理的内容在我的博客:C语言——动态内存管理 中有比较详细的介绍。欢迎各位大佬能阅读一下。

C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过 new delete 操作符进行动态内存管理。

1.new/delete操作内置类型

new 运算符用于动态分配内存,并返回指向该内存的指针。它可以与单个对象或对象数组一起使用。

delete 运算符用于释放之前由 new 运算符分配的内存。它必须与 new 运算符成对使用,以避免内存泄漏。

使用示例如下:

int main()
{// 动态分配一个整型变量的内存,并将地址赋给指针p1 int* p1 = new int;// 动态分配一个包含10个整型变量的数组的内存,// 并将数组首地址赋给指针p2int* p2 = new int[10];// 注意:这里的数组也是未初始化的,数组中的所有元素都是未定义的。// 动态分配一个整型变量的内存,并初始化为10,然后将地址赋给指针p3int* p3 = new int(10);// 这里,p3指向的整型变量被明确初始化为10。//动态申请5个int类型空间,并将前面2个初始化为1,后面默认初始化为0int* p4 = new int[5] {1, 1};//与C语言free功能类似,释放空间防止内存泄漏delete p1;delete[] p2;delete p3;delete[] p4;return 0;
}

我们可以通过监视窗口来观察一下:

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

使用new和delete来操作内置类型(如int、float、char等)时,它们在底层的行为上与C语言中的malloc和free没有太大的本质区别。这主要体现在它们都是用来在堆上分配和释放内存的。然而,它们之间还是存在一些关键的区别和考虑因素,尤其是在处理自定义类型时。

来看看这段代码:

class A
{
public:A(int a = 0):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}void Print(){cout << "_a = " << _a << endl;}
private:int _a = 0;
};int main()
{A* p1 = new A;A* p2 = new A(10);p1->Print();p2->Print();delete p1;delete p2;return 0;
}

运行结果为:

我们可以得知:new在创建自定义类型时会自动调用其构造函数,delete在释放其空间时会自动调用其析构函数。

operator new与operator delete函数

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

下面是operator new与operator 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);
}
/*
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_FINALLYreturn;
}

在C++中,operator new 和 operator delete 是全局函数,它们分别用于内存的申请和释放。这些函数在内部通常会使用C语言风格的内存管理函数(如malloc和free)来执行实际的内存分配和释放操作,但它们提供了额外的功能,特别是异常处理和类型安全。

operator new可以配置为在内存分配失败时抛出一个std::bad_alloc异常,而不是简单地返回nullptr。这允许C++程序以一种更加面向对象和异常安全的方式来处理内存分配失败的情况。

operator delete 最终是通过free来释放空间的。

new与delete的实现

接下来我们来探讨一下new与delete的实现,来看一下这段代码:

class A
{
public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
private:int _x = 0;
};void Test()
{int* ptr1 = new int;	//内置类型A* ptr2 = new A;		//自定义类型delete ptr1;delete ptr2;
}

我们通过反汇编语言来观察一下:

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表达式

定位new表达式(placement new)是一种特殊的C++语法,允许开发者在已分配但未经初始化的内存区域上构造对象。

定位new的基本使用格式如下:

new (place_address) type;

或者,如果需要传递初始化列表给构造函数,可以这样做:

new (place_address) type(initializer-list);

place_address 是一个指向足够大且已分配(但可能未初始化)的内存块的指针,用于存储新构造的对象。

type 是要构造的对象的类型。

initializer-list 是一个可选的初始化列表,用于传递给对象的构造函数。

下面是个简单的使用示例:

class A 
{
public:A(int a = 0) : _a(a)  // 使用初始化列表显式初始化成员变量_a {cout << "A(int a = 0):" << _a << endl;}// 析构函数~A(){cout << "~A()\n";}
private:int _a;
};int main() 
{// 分配内存    void* memory = operator new(sizeof(A));// 使用定位new构造对象:在已分配的内存上构造A类的对象A* a = new (memory) A;// 显式调用析构函数    a->~A();// 释放内存    operator delete(memory);return 0;
}

输出结果为:

malloc/free和new/delete的区别

(1)malloc和free是函数,new和delete是操作符。
(2)malloc申请的空间不会初始化,new可以初始化。
(3)malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
(4)malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
(5)malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
(6)申请自定义类型对象时,malloc / free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

结束语

写的有点墨迹了。。。

求点赞收藏评论关注!!!

感谢各位大佬支持!!!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 828华为云征文|部署知识库问答系统 MaxKB
  • Sqlserver常用sql
  • list从0到1的突破
  • Guava中Preconditions校验
  • Android中的冷启动,热启动和温启动
  • 一款自动对杂乱文件进行整理和分类的AI工具--FileNeatAI
  • python之排列组合1
  • RecyclerView的子项长按选择功能
  • Node.js运行环境搭建
  • 苹果CMS海洋CMS那个更容易被百度收录?苹果CMS站群
  • 记录小数点
  • 001 RabbitMQ入门及安装
  • 浏览器插件利器--allWebPluginV2.0.0.20-beta版发布
  • 2024 批量下载知乎回答/文章/想法/专栏/视频/收藏夹,导出 excel 和 pdf
  • 运维工程师面试整理-操作系统
  • #Java异常处理
  • 【前端学习】-粗谈选择器
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • eclipse(luna)创建web工程
  • EOS是什么
  • input实现文字超出省略号功能
  • Java IO学习笔记一
  • js面向对象
  • MD5加密原理解析及OC版原理实现
  • miaov-React 最佳入门
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • python_bomb----数据类型总结
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • 给新手的新浪微博 SDK 集成教程【一】
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 前端路由实现-history
  • 前端面试总结(at, md)
  • 三分钟教你同步 Visual Studio Code 设置
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 微信开源mars源码分析1—上层samples分析
  • 小程序button引导用户授权
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • ()、[]、{}、(())、[[]]命令替换
  • (1)无线电失控保护(二)
  • (14)Hive调优——合并小文件
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (7)STL算法之交换赋值
  • (pojstep1.1.2)2654(直叙式模拟)
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (二)延时任务篇——通过redis的key监听,实现延迟任务实战
  • (分享)自己整理的一些简单awk实用语句
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (算法)大数的进制转换