C++初阶之内存分布(下)

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

new和delete的实现原理


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


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


使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表


使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。


#include <iostream>
using namespace std;
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;
}


dfa72f895f3b4e479f57710fc1857859.png


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


内存泄漏


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;
}


2.内存泄漏分类


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


堆内存泄漏(Heap leak)


堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。


系统资源泄漏


指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。


3.如何检测内存泄漏


在vs下,可以使用windows操作系统提供的**_CrtDumpMemoryLeaks() 函数**进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。


int main()
{
  int* p = new int[1024];
  // 将该函数放在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.


因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。


4.如何避免内存泄漏


1.工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。(这是理想状态,但是如果碰上异常时,就算注意释放了,还是可能会出问题,需要下一条智能指针来管理才有保证)

2.采用RAII思想或者智能指针来管理资源。

3.有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

4.出问题了使用内存泄漏工具检测。(不过很多工具都不够靠谱,或者收费昂贵)


总结:

内存泄漏非常常见,解决方案分为两种:


1.事前预防型。如智能指针等。

2.事后查错型。如泄漏检测工具。


结语


有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!

制作不易,如有不正之处敬请指出

感谢大家的来访,UU们的观看是我坚持下去的动力

在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!

04fb7bb7c9084763be5e337fe2c1dd8b.png

相关文章
|
8天前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
25 2
|
2月前
|
存储 编译器 C语言
【C++】C\C++内存管理
【C++】C\C++内存管理
【C++】C\C++内存管理
|
4天前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
19 5
|
2月前
|
存储 安全 编译器
Go 内存分布
该文章深入分析了Go语言中值的内存分布方式,特别是那些分布在多个内存块上的类型,如切片、映射、通道、函数、接口和字符串,并讨论了这些类型的内部结构和赋值时的行为,同时指出了“引用类型”这一术语在Go中的使用可能会引起的误解。
48 5
Go 内存分布
|
4天前
|
C++
超级好用的C++实用库之动态内存池
超级好用的C++实用库之动态内存池
11 0
|
2月前
|
编译器 C++
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
|
2月前
|
存储 Java C语言
【C++】C/C++内存管理
【C++】C/C++内存管理
|
1月前
|
C语言 C++
C++(二)内存管理
本文档详细介绍了C++中的内存管理机制,特别是`new`和`delete`关键字的使用方法。首先通过示例代码展示了如何使用`new`和`delete`进行单个变量和数组的内存分配与释放。接着讨论了内存申请失败时的处理方式,包括直接抛出异常、使用`try/catch`捕获异常、设置`set_new_handler`函数以及不抛出异常的处理方式。通过这些方法,可以有效避免内存泄漏和多重释放的问题。
|
2月前
|
存储 编译器 C语言
C++内存管理(区别C语言)深度对比
C++内存管理(区别C语言)深度对比
72 5
|
2月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
40 0