首页 > 系统相关 >内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?

时间:2023-05-30 21:01:06浏览次数:46  
标签:初始化 malloc 缓存 int C++ 内存 new delete

一、c/c++程序内存区域划分

c和c++的内存区域划分是十分相似的,因为c++是完全兼容c语言,是c语言的面向对象的升级版。

接下来看如下图:

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存管理

程序的内存区域被划分成6个区域。内核空间、栈、内存映射段、堆、数据段、代码段。

下面是对相关内存区域名词解释:

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


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

常见的函数有:malloc/calloc/realloc/free

看下面这一段代码我们来进行剖析和回顾

#include<iostream>
using namespace std;
int main()
{
	//malloc 动态申请4个字节的空间
	int* p1 = (int*)malloc(sizeof(int));
	// 释放内存空间 
	free(p1);

	// calloc   给定初始化数据类型的个数   并都初始化为0
	int* p2 = (int*)calloc(4, sizeof(int));

	//realloc   在原有的分配的动态内存上进行扩容  (原地扩容/异地扩容)
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
	
	//释放内存
	free(p3);


	return 0;
}

1、为什么可以不用free(p2),而只释放p3即可?

因为这跟realloc的机制和动态内存的释放机制有关,动态内存的释放我们要从头开始释放整个开辟的连续的空间,如果调用realloc进行扩容发现后面还有足够的内存空间可以进行扩容,那么就是原地扩容,p3这个指针就是代表p2动态开辟的内存+realloc出来的内存空间的头,所以就只要释放p3。当realloc是进行的异地扩容那么就都要释放。

2、那么我先释放p3,再释放p2,可以吗?

原地扩容释放p3后,再释放p2,释放p2的时候是找不到地址的,会出现报错。


3、malloc/calloc/realloc的区别?

malloc、calloc、realloc三者主要区别在于分配内存的方式和初始化。malloc只分配内存,不进行初始化;calloc分配内存并初始化为0;而realloc则重新调整已经分配内存块的大小。另外,realloc() 函数需要传入先前分配的内存地址,而 malloc() 和 calloc() 则不需要。需要注意的是,这些函数都可以引起内存泄漏,必须在使用完后释放分配的内存区域。

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

c++中也还是可以用c语言的内存的管理方式,但是在面向对象过程中太过于繁琐,所以c++建立了一套新的内存管理方式:通过new和delete操作符进行动态内存管理。

实例代码:

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

在上面代码里面可以看到int(10),这个很熟悉。这个是像是对象的初始化。这里我们可以探究一下new这个操作符是不是给int进行了初始化。

通过调试如下图:

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_new_02

可以看出来new int(10) ,是进行了初始化的,那么问题来了,new int会不会进行初始化呢,我们知道构造函数和拷贝构造是对内置类型不做处理的。那么如果进行了初始化的话,就是一个随机数。

下面是代码调试:

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存泄漏_03

我们可以看到p2是被初始化为-842150451这个随机数了,还是可以佐证new这个操作符进行了那些优化,可以进行对对象的初始化操作。

new对对象的实例化

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存泄漏_04

这里注意:对对象数组的管理,因为是申请和释放连续的空间,使用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)); // C
 int* 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的时候先主动调用析构函数对资源进行清理。再释放空间。


四、c&c++动态内存管理对比

4.1malloc/new&&free/delete

4.1.1 在内置类型对象的初始化没有区别 

我们知道malloc在申请int类型的内存空间的时候,是只申请空间没有进行初始化,同样的new操作符在申请空间的时候是进行调用构造函数,构造函数对内置类型是不进行处理的,也就是和malloc没有区别。

4.1.2 new和delete特性

用new进行自定义类型的对象创建是自动调用构造函数 

delete 的时候对自定义类型的对象是自动调用析构函数

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_05


4.1.3 自定义数组初始化问题

当我new了一个类类型的数组的时候,再进行delete,会进行重复调用构造函数和析构函数

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_06

特别注意的是 自定义类型定义的数组,需要有默认构造,供其调用,否者会提示编译不通过

如图:

1、在构造函数给缺省值
#include<iostream>
using namespace std;
class A
{
public:

	A(int a=10)
		:_a(a)
	{
		cout << "A(int a)" <<this<< endl;
	}
	~A()
	{
		cout << "~A()"<<this << endl;
	}

private:
	int _a;



};
int main()
{
	int* p1 = new int;      //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量

	int* p2 = new int[3];   //在堆上开了三个int类型的变量、

	delete p1;
	delete[] p2;

	A* p3 = (A*)malloc(sizeof(A));
	A* p4 = new A(1);
	free(p3);
	delete(p4);

	A* p5 = new A[10];   //可见这里的对象都被初始化为10
	delete[] p5;

	return 0;
 }

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_new_07

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存泄漏_08


2、c++11特性 在成员变量声明给缺省值 然后调用默认构造初始化
#include<iostream>
using namespace std;
class A
{
public:

	A(int a)
		:_a(a)
	{
		cout << "A(int a)" <<this<< endl;
	}
	~A()
	{
		cout << "~A()"<<this << endl;
	}

private:
	int _a=10;



};
int main()
{
	int* p1 = new int;      //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量

	int* p2 = new int[3];   //在堆上开了三个int类型的变量、

	delete p1;
	delete[] p2;

	A* p3 = (A*)malloc(sizeof(A));
	A* p4 = new A(1);
	free(p3);
	delete(p4);

	A* p5 = new A[10];  //这里也是全部被初始化为10
	delete[] p5;

	return 0;
 }

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_09

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存管理_10


3、如果没有默认构造,会进行报错,“没有合适默认构造可用”

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存管理_11

4、解决方案

解决方案:主动进行初始化,在创建自定义类型对象的时候加上{},初始化

#include<iostream>
using namespace std;
class A
{
public:

	A(int a)
		:_a(a)
	{
		cout << "A(int a)" <<this<< endl;
	}
	~A()
	{
		cout << "~A()"<<this << endl;
	}

private:
	int _a;



};
int main()
{
	int* p1 = new int;      //在堆上开了一个int类型的变量 然后在栈里面是一个p1 指向堆的这个变量

	int* p2 = new int[3];   //在堆上开了三个int类型的变量、

	delete p1;
	delete[] p2;

	A* p3 = (A*)malloc(sizeof(A));
	A* p4 = new A(1);
	free(p3);
	delete(p4);

	A* p5 = new A[10]{1,2,3,4,5,6,7,8,9,10};
	delete[] p5;

	return 0;
 }

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_12

注意:在主动进行初始化的时候要注意如果是多参数的话,就不能用隐式类型转化了,要显示的写出来入

#include<iostream>
using namespace std;
class A
{
public:

	A(int a,int b)
		:_a(a)
		,_b(b)
	{
		cout << "A(int a)" <<this<< endl;
	}
	~A()
	{
		cout << "~A()"<<this << endl;
	}

private:
	int _a;
	int _b;


};
int main()
{

	A* p5 = new A[4]{A(1,2),A(1,2),A(1,2),A(1,2)};
	delete[] p5;

	return 0;
 }

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_delete_13

4.2 使用原则

原则:一定要匹配使用

malloc 和free 对应 

new 和 delete对应 

malloc 和new除了用法上面的区别 ,还有一个重大的区别 就是new和delete会调用构造函数初始化,析构函数清理资源。

 

4.3 operator new 和 operator delete 函数 

全局函数 ----来自库里面的函数 

int main()
{

	int* p1 = (int*)malloc(sizeof(int));

	int* p2 = new int;
	operator delete (p1);
	delete p2;

	A* p3 = (A*)operator new (sizeof(A));
	A* p4 = new A(1, 2);
	operator delete (p3);
	delete (p4);



	return 0;
 }

总的来说,new   开空间+构造函数     (new开空间是不能直接调用malloc 因为c++需要去捕获异常)

malloc 开空间

delete    析构函数 + 释放空间

free  释放空间

4.3.1 new 和 delete的实现原理

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_14

operator new 和 operator delete是全局函数,在我们用new和delete的时候是调用的这个两个全局函数,来申请和释放空间。也就是说new和delete在底层申请空间和释放的时候还是调用的malloc和free。

operator new 的好处:面向对象语言处理失败,不喜欢返回值,更建议抛异常 。operator new 当申请的空间失败,会执行我们设定的保护措施,否者就是会抛出异常。

4.4 大内存运行对比

先用malloc试试

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存管理_15

内存直接爆满接近6G~~~ 基本上malloc不会失败

但是当换到new 

int main()
{

	int* p1 = nullptr;

	do
	{
	/*	p1 = (int*)malloc(1024 * 1024);*/
		p1 = new int[1024 * 1024];
		cout << p1 << endl;
	} while (p1);



	return 0;
}

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_c++_16

先是直接内存爆满然后黑屏,给我吓一跳,幸好是win系统下面是给我分配的虚拟内存(一种保护机制吧)然后抛出未经处理的异常 

解决办法:一般是要用try ,catch来捕获异常

int main()
{

	int* p1 = nullptr;
	try
	{
		do
		{
			/*	p1 = (int*)malloc(1024 * 1024);*/
			p1 = new int[1024 * 1024];
			cout << p1 << endl;
		} while (p1);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

内存泄漏、缓存溢出?C和C++,哪个更懂得管理内存质量?_内存管理_17

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

六、内存泄漏

6.1什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现

内存泄漏会导致响应越来越慢,最终卡死。

最严重的是在服务器里面跑的程序每次泄漏一点内存,时间长了也不能发现,就是觉得变卡了。

6.2内存泄漏分类

  • 堆内存泄漏(Heap leak)

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

  • 系统资源泄漏

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

6.3如何避免内存泄漏

工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。

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

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

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

总结一下:

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

标签:初始化,malloc,缓存,int,C++,内存,new,delete
From: https://blog.51cto.com/u_15831056/6368962

相关文章

  • 前端浏览器缓存和HTTP缓存
    缓存缓存优点:减少冗余的数据传输;减轻服务器的压力;加快浏览器加载网页的速度。分类:强缓存和协商缓存 强缓存:服务器不需要发送资源给客户端,客户端直接从缓存中取有关头字段:Cache-Control、Expires,两者同时存在时,前者优先级更高Expires:当客户端向服务器发送请求,服务......
  • 为什么 C++ 有指针了还要引用
    引用传递,只是明面上,没有使用值传递,值传递本身是不可避免的。编译器,暗地里通过指针(或者其他可以替代指针的数据类型)的值传递,替换了引用传递。所以引用传递,实质上是地址传递,别名这东西只是概念,是一种抽象,别名是没法传递的。别名,可不是真实的数据类型。因为,函数传递参数需要,数据复制,......
  • 腾讯二面:有 40 亿个 QQ 号,限制 1G 内存,问如何去重?被问懵了!
    40亿个QQ号,限制1G内存,如何去重?40亿个unsignedint,如果直接用内存存储的话,需要:4*4000000000/1024/1024/1024=14.9G,考虑到其中有一些重复的话,那1G的空间也基本上是不够用的。想要实现这个功能,可以借助位图。使用位图的话,一个数字只需要占用1个bit,那么40亿个数字也就是:400......
  • C++中模拟split
    #include<iostream>#include<sstream>usingnamespacestd;intmain(){ stringstr; getline(cin,str); istringstreamin(str); stringa; while(getline(in,a,'*')){ cout<<a<<''; } return0;}123*456*789123......
  • 蓝桥杯 基础练习 特殊回文数(C++)
    资源限制内存限制:512.0MBC/C++时间限制:1.0sJava时间限制:3.0sPython时间限制:5.0s问题描述123321是一个非常特殊的数,它从左边读和从右边读是一样的。输入一个正整数n,编程求所有这样的五位和六位十进制数,满足各位数字之和等于n。输入格式输入一行,包含一个正整......
  • C++ 不想让转义字符发挥转义的功能
    今天写代码时,编译器有一个警告:我寻思着也没啥问题,于是就看了一下警告,然后回车,就成了这样,也就是说,字符串里面的转义字符不再时转义字符而是普通的字符了,输出看看是不是:果然是这样没错.......
  • 第十四届蓝桥杯大赛青少组全国总决赛初级组C++C++题解
    第十四届蓝桥杯大赛青少组全国总决赛初级组\(C++\)题解第一题给定一个十进制正整数\(N(1≤N≤10^9)\),请从小到大输出\(1\)~\(N\)之间(含\(1\)和\(N\))所有满足以下要求的数:这个数转换为八进制后是一个回文数;这个数是一个平方数。例如:\(N=20\),在\(1\)~\(20\)之间满足要求......
  • 如何用ReadWriteLock实现一个通用的缓存中心?
    摘要:在并发场景中,JavaSDK中提供了ReadWriteLock来满足读多写少的场景。本文分享自华为云社区《【高并发】基于ReadWriteLock开了个一款高性能缓存》,作者:冰河。写在前面在实际工作中,有一种非常普遍的并发场景:那就是读多写少的场景。在这种场景下,为了优化程序的性能,我们经常使......
  • 2.6. Java内存管理与垃圾回收
    2.6.1.Java内存模型在Java中,内存被划分为以下几个区域:堆(Heap):存储对象实例和数组,是垃圾回收的主要区域。栈(Stack):存储局部变量和方法调用。每个线程有自己的栈。方法区(MethodArea):存储类信息,如类的结构、方法、字段等。本地方法栈(NativeMethodStack):存储本地方法(如JNI)的调用......
  • yolov5内存分布分析
    yolov5内存分布分析Transpose输出分析假设batch_size为1,yolov5有三个输出,shape分别是:(1,3,80,80,85)(1,3,40,40,85)(1,3,20,20,85)其中3代表anchor数量,20*20代表feature_map大小,85代表boundbox的(x,y,w,h,c+80个类别的概率)其中(x,y,w,h,c+80个类别的概率)在内存中是连续分......