C/C++内存管理学习【new】

文章目录

    • 一、C/C++内存分布
    • 二、C语言中动态内存管理方式:malloc/calloc/realloc/free
    • 三、C++内存管理方式
    • 3.1 new/delete操作内置类型
    • 3.2 new和delete操作自定义类型
    • 四、operator new与operator delete函数
    • 五、new和delete的实现原理
      • 5.1 内置类型
    • 六、定位new表达式(placement-new)
    • 七、常见面试题
      • 7.1 内存泄漏
      • 7.2 内存泄漏分类
      • 7.3 如何检测内存泄漏
      • 7.4 如何避免内存泄漏

一、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);
}
  1. 选择题:

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

  • globalVar在哪里?C
  • staticGlobalVar在哪里?C
  • staticVar在哪里?C
  • localVar在哪里?A
  • num1 在哪里?A

  • char2在哪里?A
    • 字符串在常量区,char2会栈上开辟一个数组,然后将常量区的字符串拷贝过到栈上,所以结果是A,上题的num1是直接在栈上开辟的,所以相似
  • *char2在哪里?A
    • 这个上题说过了不讲了
  • pChar3在哪里?A
    • const修饰的是代表常变量,不代表就在常量区,pChar3是栈上的一个变量,指向了常量区的字符串
  • *pChar3在哪里?D
    • 常量区的字符串,解引用就是找的常量区
  • ptr1在哪里?A
    • 也是栈上的一块变量
  • *ptr1在哪里?B
    • 开辟的空间在堆上

  1. 填空题:
  • sizeof(num1) = 40;
  • sizeof(char2) = 5;
  • strlen(char2) = 4;
  • sizeof(pChar3) = 4/8;
  • strlen(pChar3) = 4;
  • sizeof(ptr1) = 4/8;
  1. sizeof 和 strlen 区别?
  • 这里具体可以移步到C语言指针章节,最后面有详细的讲解

在这里插入图片描述

【说明】

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量。

  • 为什么要分这些区域

    • 为了方便管理,程序中有各种不同的数据
  • 这些区域哪个区域是我们需要重点关注的?

    • 堆【堆留给我们自己自主控制的】

二、C语言中动态内存管理方式:malloc/calloc/realloc/free

void Test()
{int* p1 = (int*)malloc(sizeof(int));free(p1);// 1.malloc/calloc/realloc的区别是什么?int* p2 = (int*)calloc(4, sizeof(int));int* p3 = (int*)realloc(p2, sizeof(int) * 10);// 这里需要free(p2)吗?free(p3);
}

【面试题】

  • malloc/calloc/realloc的区别?

    • malloc是在内存中直接开辟一块空间
    • calloc会在开辟的时候进行初始化【初始化成0】
    • realloc可以进行已开辟的空间进行扩容

三、C++内存管理方式

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

3.1 new/delete操作内置类型

void Test()
{// 动态申请一个int类型的空间int* ptr4 = new int;// 动态申请一个int类型的空间并初始化为10int* ptr5 = new int(10);// 动态申请10个int类型的空间int* ptr6 = new int[3];delete ptr4;delete ptr5;delete[] ptr6;
}

在这里插入图片描述

在这里插入图片描述

  • new10个对象进行初始化,后面跟上大括号进行即可
int* p2 = new int[10] {1, 2, 3, 4, 5, 6, 7};	

在这里插入图片描述

  • 这里要一定注意,要匹配着使用

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

3.2 new和delete操作自定义类型

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数A* p1 = (A*)malloc(sizeof(A));A* p2 = new A(1);free(p1);delete p2;// 内置类型是几乎是一样的int* p3 = (int*)malloc(sizeof(int)); // Cint* p4 = new int;free(p3);delete p4;A* p5 = (A*)malloc(sizeof(A) * 10);A* p6 = new A[10];free(p5);delete[] p6;return 0;
}

在这里插入图片描述

小结:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

  • 在我们使用malloc的时候,都需要手动检查,而new失败了就会抛异常
struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr), _val(val){}
};// 创建的不带哨兵位
ListNode* CreateList(int n)
{ListNode head(-1);  // 哨兵位ListNode* tail = &head;int val;printf("请依次输入%d个节点的值:>", n);for (int i = 0; i < n; i++){cin >> val;tail->_next = new ListNode(val);tail = tail->_next;}return head._next;
}void func()
{int n = 1;while (1){int* p = new int[1024 * 1024 * 100];cout << n << "->" << p << endl;++n;}
}int main()
{// 1、用法上,变简洁了int* p0 = (int*)malloc(sizeof(int));int* p1 = new int;int* p2 = new int[10]; // new 10个int对象// 2、可以控制初始化int* p3 = new int(10); // new 1个int对象,初始化成10int* p4 = new int[10] { 1, 2, 3, 4, 5 };// 3、自定义类型,开空间+构造函数// 4、new失败了以后抛异常,不需要手动检查ListNode* node1 = new ListNode(1);ListNode* node2 = new ListNode(2);ListNode* node3 = new ListNode(3);//...ListNode* list1 = CreateList(5);delete p3;delete[] p4;delete p1;delete[] p2;// 抛异常try{func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

四、operator new与operator delete函数

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

  • operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

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;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
  • 通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

  • 我们通过汇编也可以看到,这些最后还是分别调用malloc和free的

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • operator new对malloc 的封装,失败抛异常实现new
  • operator delete对free的封装
class Stack
{
public:Stack(){_a = (int*)malloc(sizeof(int) * 4);_top = 0;_capacity = 4;}~Stack(){free(_a);_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;
};int main()
{A* ptr1 = new A;  // operator new + 1次构造A* ptr2 = new A[10]; // operator new[] + 10次构造delete ptr1; // 1次析构 + operator deletedelete[] ptr2; // 10次析构 + operator delete[]Stack* pst = new Stack;delete pst;return 0;
}
  • delete是先析构再释放这块空间

  • 那么在使用自定义类型进行new的时候会在头部多开4个字节存放个数,这就是为什么在delete的时候要匹配着加[],还需要调用析构函数,这样就知道要释放多少了

在这里插入图片描述

  • 通过内存监视窗口再看一下

在这里插入图片描述

  • 如果不显示写这个析构函数,就也不会多开4个字节,编译器会自动生成一个,编译器觉得什么也不干,自动就会优化掉了,也就不会调用了

  • 而内置类型就不会多开,内置类型就不用调用析构函数

在这里插入图片描述

五、new和delete的实现原理

5.1 内置类型

  • 如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

5.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表达式(placement-new)

  • 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

  • new (place_address) type或者new (place_address) type(initializer-list)
  • place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

  • 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};// 定位new/replacement new
int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行A* p1 = (A*)malloc(sizeof(A));new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();free(p1);A* p2 = (A*)operator new(sizeof(A));new(p2)A(10);p2->~A();operator delete(p2);return 0;
}

七、常见面试题

  • malloc/free和new/delete的区别

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在释放空间前会调用析构函数完成空间中资源的清理

7.1 内存泄漏

  • 什么是内存泄漏,内存泄漏的危害
  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.delete[] p3;
}

7.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)
    • 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏
    • 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

7.3 如何检测内存泄漏

  • 在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息
int main()
{int* p = new int[10];// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();return 0;
}

// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
  • 因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。

7.4 如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

内存泄漏非常常见,解决方案分为两种:
1、事前预防型。如智能指针等。
2、事后查错型。如泄漏检测工具。

最后本文就到这里结束了,感谢大家的收看,请多多指点~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/699226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

后端经典面试题合集

目录 1. Java基础1-1. JDK 和 JRE 和 JVM 分别是什么&#xff0c;有什么区别&#xff1f;1-2. 什么是字节码&#xff1f;采用字节码的最大好处是什么&#xff1f; 1. Java基础 1-1. JDK 和 JRE 和 JVM 分别是什么&#xff0c;有什么区别&#xff1f; JDK 是Java开发工具包&am…

基于ssm框架的高校班级管理系统设计与实现

为解决当前高校班级管理中管理方式落后、手段落后及效率低下等问题而以当前主流的互联网技术设计一款高校班级管理系统。该系统采用B/S模式的设计思路而将前端&#xff08;JSP技术&#xff09;和后端&#xff08;SSM框架MySQL数据库&#xff09;整合于一体并通过Java语言代码编…

VSCODE使用Django 页面和渲染

https://code.visualstudio.com/docs/python/tutorial-django#_use-a-template-to-render-a-page 通过模板渲染页面 文件 实现步骤 1&#xff0c; 修改代码&#xff0c;hello的App名字增加到installed_apps表中。 2&#xff0c; hello子目录下&#xff0c;创建 .\templates\…

常见的序列化数据结构方法及其优缺点汇总

文章目录 1. JSON (JavaScript Object Notation)2. XML (eXtensible Markup Language)3. YAML (YAML Aint Markup Language)4. Protobuf (Protocol Buffers)5. MessagePack6. BSON (Binary JSON)7. Avro8. Thrift9. CBOR (Concise Binary Object Representation) 将常见的序列化…

配置DM-Multipath多路径存储

1、申请并挂载共享存储 2、安装multipadthd服务 [root@localhost ~]# yum install -y multipadthd [root@localhost ~]# systemctl enable multipadthd [root@localhost ~]# yum list |grep multipadthd [root@localhost ~]# systemctl list-unit-files -t service |grep mul…

vue中使用echarts绘制双Y轴图表时,刻度没有对齐的两种解决方法

文章目录 1、原因2、思路3、解决方法3.1、使用alignTicks解决3.2、结合min和max属性去配置interval属性1、首先固定两边的分隔的段数。2、结合min和max属性去配置interval。 1、原因 刻度在显示时&#xff0c;分割段数不一样&#xff0c;导致左右的刻度线不一致&#xff0c;不…

全栈开发(TS,React,Vue, Java, 移动端flutter)接单

个人主页 https://hz.minicv.net/ 技术栈 前端&#xff1a;NextJS React VueJS 后端&#xff1a;NestJS Java 移动端&#xff1a;Flutter 其他&#xff1a;SpringCloud Redis Kafka Zookeeper 项目案例 微行简历&#xff08; TS 全栈项目&#xff0c;一个极简的简历管理平…

【PHP设计模式03】抽象工厂模式

【抽象工厂模式】 前面讲的工厂方法模式,每增加一个产品都需要增加对应的工厂,这样就会存在很多工厂,但是在现实生活中的有些时候,人们并不关心产品是由哪个工厂制造的,就像一个服装厂既可以生产男装,也可以生产女装,我们只需造一个服装厂同时生产一个产品族下的男装和女…

【网络编程】从Retrofit原理来看HTTP

文章目录 create()validateServiceInterface()动态代理loadServiceMethod(method) create() 让我们先深入到create 里&#xff0c; public <T> T create(final Class<T> service) {validateServiceInterface(service);.... }validateServiceInterface() 我们发现…

【Java】Java基础(实验一)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 掌握Java程序的编辑、调试与运行&#xff1b;了解Java引用类型&#xff0c;掌握数组的定义和引用。掌握Java基本数据类型和输入输出。掌握Java程序结构 二、实验内容 1.JDK的环境变量设置及测试。 &#xff08…

HarmonyOS Stage模型 应用配置文件讲解

好&#xff0c;上文 HarmonyOS Stage模型基本概念讲解 中&#xff0c;我们简单讲解了HarmonyOS 中 Stage模型的基本概念 那么 我们继续学习Stage模型的相关知识 上文之后 我们肯定对它的概念和基本结构 有了一个了解 那么 我们就来看一下 基于Stage模型 它里面一些基本的配置文…

InnoDB基础篇(4)-数据类型在InnoDB中的选择

在MySQL数据库中&#xff0c;数据类型在InnoDB存储引擎中的选择是非常重要的。选择合适的数据类型可以在存储空间、性能和数据完整性方面取得优化。在本篇博客中&#xff0c;我们将介绍一些常见的MySQL数据类型&#xff0c;并讨论它们在InnoDB中的选择。 整数类型&#xff08;I…

15-36V降压充电光伏MPPT充电方案

1.MPPT原理--简介 MPPT&#xff0c;全称为Maximum Power Point Tracking&#xff0c;即最大功点跟踪&#xff0c;它是一种通过调节电气模块的工作状态&#xff0c;使光伏板能够输出更多电能的电气系统能够将太阳能电池板发出的直流电有效地贮存在蓄电池中&#xff0c;可有效地…

C++内联函数的使用

C是一门强大而灵活的编程语言&#xff0c;提供了许多特性来优化代码性能。其中之一就是内联函数&#xff0c;它可以在编译时将函数调用的地方直接替换为函数体&#xff0c;从而减少函数调用的开销。在本文中&#xff0c;我们将深入探讨C内联函数的使用&#xff0c;重点介绍它的…

uniapp editor组件添加插入超链接

标题uniapp editor组件添加插入超链接 我基于官方editor组件开发了一个可添加超链接等其他功能的富文本插件 官方富文本编辑器editor组件改良扩展优化版

视频推拉流EasyDSS视频直播点播平台授权出现激活码无效并报错400是什么原因?

视频推拉流EasyDSS视频直播点播平台集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务&#xff0c;在应用场景上&#xff0c;平台可以运用在互联网教育、在线课堂、游戏…

32.云原生Istio流量管理之官网Bookinfo应用实战演示

云原生专栏大纲 文章目录 流量管理基于版本的路由配置基于 Http header 的路由配置故障注入延迟故障注入异常故障注入故障注入测试 比例分配流量请求超时熔断什么是熔断创建 httpbin 服务创建访问者服务 流量管理 Istio 是服务治理的工具&#xff0c;Istio 的流量管理能力&am…

Django学习笔记-forms使用

1.创建forms.py文件,导入包 from django import forms from django.forms import fields from django.forms import widgets2. 创建EmployeeForm,继承forms.Form 3.创建testform.html文件 4.urls.py添加路由 5.views中导入forms 创建testform,编写代码 1).如果请求方式为GET,…

unity学习(38)——创建(create)角色脚本(panel)--EventSystem

1.在scripts文件夹下创建一个脚本CreatePlayerPanel.cs&#xff0c;脚本挂到panel上&#xff01;给panel加个tag&#xff0c;叫createPanel&#xff0c;脚本内容如下&#xff1a; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngin…

BM100 设计LRU缓存结构(java实现)

一、题目 设计LRU(最近最少使用)缓存结构&#xff0c;该结构在构造时确定大小&#xff0c;假设大小为 capacity &#xff0c;操作次数是 n &#xff0c;并有如下功能: Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存get(key)&#xff1a;如果关键字 key …