【 C++ 】C/C++内存管理

前言:

😘我的主页:OMGmyhair-CSDN博客

目录

一、C/C++内存分布

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

malloc:

calloc:

realloc:

free:

三、C++内存管理方式

1.用new/delete操作内置类型

2.用new/delete操作自定义类型

[3.operator new和operator delete函数](#3.operator new和operator delete函数)


一、C/C++内存分布

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

malloc:

在c语言中,我们可以使用malloc进行动态申请内存:

cpp 复制代码
int main()
{
	int* p = (int*)malloc(sizeof(int) * 2);
	return 0;
}

从上面我们可以看到我们申请了8个字节大小的空间。malloc函数的返回值是void*类型的指针,指向已经开辟好的空间的首地址。我们可以通过强转,转为自己需要的类型。我们用malloc申请到的空间,里面没有初始化,值是不确定的。

如果申请空间失败,将会返回空指针。


calloc:

cpp 复制代码
int main()
{
	//malloc:
	int* p = (int*)malloc(sizeof(int) * 2);

	//calloc:
	int* pc = (int*)calloc(2, sizeof(int));

	return 0;
}

在上面代码中,我们用calloc申请了2个大小为int的空间。calloc函数的返回值是void*类型的指针,指向已经开辟好的空间的首地址。它与malloc的区别在于,calloc申请到的空间每个比特位都会初始化为0。


realloc:

cpp 复制代码
int main()
{
	//malloc:
	int* p = (int*)malloc(sizeof(int) * 2);

	//calloc:
	int* pc = (int*)calloc(2, sizeof(int));

	//realloc:
	pc = (int*)realloc(pc, sizeof(int) * 4);

	return 0;
}

realloc可以用来重新申请空间,第一个参数是原空间的地址,第二个参数是新内存块的大小

如果在原空间的地址上不能往后继续申请内存(后面的位置被占用了),那么realloc会重新开辟一块新内存空间,将原空间上的数据搬到新内存空间,并且对原空间进行释放。新内存空间对于原空间如果更大了,那么多出来的那部分是没有进行初始化的。

如果第一个参数是空指针,那么此时realloc的作用类似于malloc。

当realloc申请空间失败,会返回空指针,但是原空间依旧有效且数据还在。我们用realloc申请巨大的空间来模拟申请空间失败的情况:

cpp 复制代码
int main()
{
	int* p = (int*)calloc(2, sizeof(int));
	cout<<"realloc前p的地址:" << p << endl;
	int* pp = p;
	p = (int*)realloc(p, sizeof(int) * 1024*1024*1024*1024);
	cout <<"realloc后p的地址:" << p << endl;
	cout << "原空间的第一个数:" << pp[0] << endl;
	return 0;
}

结果:

所以我们在使用realloc的时候,要小心申请失败的情况:

cpp 复制代码
int main()
{
	int* p = (int*)calloc(2, sizeof(int));
	int* ppr = (int*)realloc(p, sizeof(int) * 24);
	if (ppr != NULL)
	{
		p = ppr;
	}
	return 0;
}

free:

如果ptr没有指向由calloc、realloc、malloc开辟的空间,那么产生的结果是不确定的。

但如果ptr是空指针,不会做任何事情。

需要注意的是,free后不会改变ptr的值,ptr依旧指向那片空间,只是此时你在去使用这块空间是非法的。


此处插播一条知识点,为什么在32位环境下指针大小是4个字节?而在64位环境下指针大小是8个字节?

举个例子,当行李箱上假设有3位密码,每一位的范围是从0~9,我们要用多少位数可以表示全部的密码呢?答案是

一个字节8个比特位。

首先,指针装的是地址,在32位系统下,内存地址空间大小是。当我们表示地址时,每一位的范围是0~1,一共32个比特位,地址也就需要用32个比特位去表示,也就是4个字节。

那么64位环境下就更好理解了。在64位系统下,内存地址空间大小是。当我们表示地址时,每一位的范围是0~1,一共64个比特位,地址也就需要用64个比特位去表示,也就是8个字节。


三、**C++**内存管理方式

1.用new/delete操作内置类型

cpp 复制代码
int main()
{
	//动态申请一个int大小的内存空间
	int* p1 = new int;

	//动态申请一个int大小的内存空间,并且初始化为1
	int* p2 = new int(1);

	//动态申请一个3个int大小的内存空间
	int* p3 = new int[3];

	//动态申请一个3个int大小的内存空间,并且初始化为0、1、2
	int* p4 = new int[3] {0, 1, 2};

	delete p1;
	delete p2;
	delete[]p3;
	delete[]p4;
	return 0;
}

当我们申请/释放单个元素的空间时,使用new/delete。当我们申请或者释放连续的空间时,使用new[]或者delete[]。注意要搭配起来使用,如果不搭配使用的后果在文章的后面会讲到。


2.用new/delete操作自定义类型

在这里,new/delete和malloc/free的区别就明显体现出来了。

cpp 复制代码
class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a1 = 1, int a2 = 2)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a1;
	int _a2;
};

int main()
{
	cout << "malloc:" << endl;
	A* ma = (A*)malloc(sizeof(A));
	cout << "-------------------------------------" << endl;

	cout << endl << "new:" << endl;
	A* na = new A;
	cout << "-------------------------------------" << endl;

	cout << endl << "free:" << endl;
	free(ma);
	cout << "-------------------------------------" << endl;

	cout << endl << "delete:" << endl;
	delete na;
	cout << "-------------------------------------" << endl;
	return 0;
}

看看运行结果:

可以看到对比malloc,new会去调用自定义类型的构造函数。而对比free,delete会去调用自定义类型的析构函数。

对于内置类型而言new/delete和malloc/free是几乎一样的。


3.operator new和operator delete函数

new delete 是用户进行 动态内存申请和释放的操作符operator new operator delete
系统提供的 全局函数new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。
对于自定义类型,new会进行 开空间+调用构造函数,而这里开空间用的是operator new来申请空间。
为什么不用malloc来申请空间呢?
如果malloc申请失败会返回空指针,而C++希望有另外一套执行来应对申请失败的情况:如果malloc申请空间成功就直接返回,否则执行用户的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
这里的operator new就相当于升级版的malloc。
对于自定义类型,delete会先析构再使用operator delete进行释放空间,而operator delete的底层是_free_dbg。
这里的_free_dbg是什么呢?
首先free其实是宏函数,它的底层是_free_dbg。

cpp 复制代码
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

总结一下:


4.new和delete的实现原理

(1)内置类型

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

cpp 复制代码
int main()
{
	//抛异常
	try
	{
		while (1)
		{
			int* p = new int[1024 * 1024];
			int* p1 = new int[1024 * 1024];
		}
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

(2)自定义类型

new和delete对于自定义类型的原理,前面已经描述过,这里就不再赘述。

(2)-1 new [N]的原理

1.调用operator new[]函数,这里的operator new[]其实就是调用operator new实现对N个对象空间申请

2.在申请的空间上进行N次构造函数

(2)-2 delete[]的原理

1.在将要释放的对象空间上调用N个析构函数,实现对N个对象的资源清理。

2.调用operator delete[]释放空间,实际上就是调用operator delete来释放空间。


5.new/delete和new[]/delete[]错误搭配以及使用free进行释放new

(1)内置类型

cpp 复制代码
int main()
{
	int* p = new int[3];
	delete p;
	return 0;
}

这个代码有什么危险吗?会产生内存泄漏吗?

答案是什么都不会发生。

因为new[]归根结底还是malloc,delete归根结底也还是free。所以在这个场景下你甚至还能用free来释放空间(不推荐)。

(2)自定义类型

1.场景1

如果我们用free去释放new的空间,编译器不会报。我们也会发现free不会调用析构函数。如果我们在析构函数中有资源的释放,可能会造成内存泄漏。

2.场景2
cpp 复制代码
class A
{
public:
	A(int a1 = 1, int a2 = 2)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a1 = 1, int a2 = 2)" << endl;
	}

private:
	int _a1;
	int _a2;
};

int main()
{
	A* p1 = new A[3];
	delete p1;
	return 0;
}

正常通过。(注意和场景3的对比)

3.场景3
cpp 复制代码
class B
{
public:
	B(int b1 = 1, int b2 = 2)
		:_b1(b1)
		, _b2(b2)
	{
		cout << "B(int b1 = 1, int b2 = 2)" << endl;
	}

	~B()
	{
		cout << "~B()" << endl;
	}
private:
	int _b1;
	int _b2;
};

int main()
{
	B* p1 = new B[3];
	delete p1;
	return 0;
}

查看运行结果:

对比与场景2,同样是用delete对new[]申请的资源进行释放,为什么B类运行时就产生了崩溃呢?

delete[]与delete的区别

我们通过比较可以发现,B跟A相比多了析构函数。当用new[]申请多个B类对象的空间时,new[]其实会在有析构函数的类前面多开出4个字节的空间来存储对象个数。因为你有析构函数,编译器怕你析构函数中有释放资源的操作,所以会记下对象的个数来多次调用析构函数避免内存泄漏。如果你没有析构函数,编译器认为没有释放资源,也就懒得给你另开空间记个数了:

A类和B类大小一样,都是8个字节,2个int成员变量。我们来看看new[]对A类开辟了多少空间,对B类又开辟了多少空间。

我们都知道不能对申请的空间只释放一部分。当B类有析构时,使用delete进行释放就是只对申请的空间释放一部分,导致了程序崩溃。所以我们在使用new/delete和new[]/delete[]时,一定要搭配使用,以免造成错误。


相关推荐
qq_589568105 分钟前
数据可视化echarts学习笔记
学习·信息可视化·echarts
CYBEREXP200817 分钟前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
ZSYP-S36 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos39 分钟前
c++------------------函数
开发语言·c++
yuanbenshidiaos43 分钟前
C++----------函数的调用机制
java·c++·算法
程序员_三木1 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
兔C1 小时前
微信小程序的轮播图学习报告
学习·微信小程序·小程序
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
海海不掉头发1 小时前
苍穹外卖-day05redis 缓存的学习
学习·缓存
tianmu_sama1 小时前
[Effective C++]条款38-39 复合和private继承
开发语言·c++