C++内存管理

文章目录

  • C++内存分布
      • 为什么栈是向下增长的,而堆是向上增长的。
  • C++中动态内存管理方式
      • new和delete操作内置类型
      • new和delete操作自定义类型
    • new返回机制
      • new和malloc的创建空间失败返回值
      • operator new和operator delete函数
  • new和delete的实现原理
      • 自定义类型
  • 定位new和表达式(placement-new)
  • 常见面试题
      • malloc / free和new /deelete的区别

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

下面是各种变量代码的内存分布:
C++内存管理_第1张图片
注意:
1: 代码 char char2[] = “abcd” 的意思为将常量字符串拷贝到数组里面。
所以char2 的内存分布在栈中。
2:代码 char
pChar3 = “abcd” 指 指针pChar3存储着常量字符串“abcd”的首元素地址。
所以*pChar3 的内存分布在静态区中。

【说明】
1:栈又叫堆栈,用于存储非静态局部变量/函数参数/返回值等,栈是向下增长。

2:堆用于存储运行时动态内存分配,堆是向上增长的。

3:数据段又叫静态区,用于存储全局数据和静态数据。

4:代码区的又叫常量区,用于存放可执行的代码和只读常量。

为什么栈是向下增长的,而堆是向上增长的。

C++内存管理_第2张图片
在一般情况下,在栈区开辟空间,先开辟的空间地址较高,而在堆区开辟空间,先开辟的空间地址较低。

让我们分别查看在栈中开辟空间的地址和在堆中开辟空间地址的情况:

#include 
using namespace std;
int main()
{
	
	int a = 10;
	int b = 20;
	cout << &a << endl;
	cout << &b << endl;

	int* c = (int*)malloc(sizeof(int)* 10);
	int* d = (int*)malloc(sizeof(int)* 10);
	cout << c << endl;
	cout << d << endl;
	return 0;
}

因为在战区开辟空间,先开辟的空间地址较高,所以打印出来a的地址大于b的地址;在堆区开辟空间,先开辟的空间地址较低,所以c指向的空间地址小于d指向的空间地址。
注意:在堆区开辟空间,后开辟的空间地址也不一定比先开辟的空间地址高。因为在堆区,后开辟的空间也有可能谓语前面某已被释放的空间位置。

C++中动态内存管理方式

new和delete操作内置类型

一:动态申请某个类型的空间

//动态申请int类型的空间。
int* p1 = new int;
delete p1;

相等于c语言的malloc

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

二:动态申请多个某类型的空间

int *p3 = new int [10];
delete [] p3;

相当于c语言的malloc

int* p3 = (int*)malloc(sizeof(int)* 10); 
	free(p3);
	p3 = NULL; 

三:动态申请某个类型的空间并初始化

int *p4 = new int(10);
delete p4;

相当于:

int* p4 = (int*)malloc (sizeof(int));
*p4 = 10;
free(p4);
p4 = NULL;

四:动态申请多个某类型的空间并初始化

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

注意:申请和释放单个元素的空间,使用new和delete操作符,申请多个空间用 new 类型[n] ,删除多个空间用 delete[] 指针变量 ; 申请并初始化用 new 类型(n),申请多个空间并初始化 new [n]{x.,x,x,x,x}.

new和delete操作自定义类型

然而对于自定义类型c语言就不适用了:

class Test
{
public:
	Test() //构造函数
		:_a(0)
	{
		cout << "构造函数" << endl;
	}
	~Test() //析构函数
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};

一:动态申请单个类的空间

 Test* p1 = new Test;
 detele p1;

使用malloc和free函数进行调用:

Test*p2 = (Test*)malloc(sizeof(Test)*10);
free(p2);

运行结果如下:
当我们在使用new来申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,然而C语言中的malloc和free并不会自动调用。
C++内存管理_第3张图片
注意:对于自定义类型,我们尽量给默认构造函数,当不传参时会自动调用,当传参时会调用构造函数。(不能只写了构造函数却不传参)
总结:
1:在申请空间时,如果申请的是内置类型的变量,那么使用malloc和new并没有什么区别。
2:对于自定义类型的对象,new 和 delete 会主动调用构造函数和析构函数,然而 malloc和free却不会,这样对不利于堆空间的释放,有可能会造成内存泄漏等问题。
3: 不管是内置类型还是自定义类型的申请和释放,尽量都用new和delete.

new返回机制

new和malloc的创建空间失败返回值

:我们先创建巨大内存,来看看malloc创建失败会返回什么。

class Test
{
public:
	Test() //构造函数
		:_a(0)
	{
		cout << "构造函数" << endl;
	}
	~Test() //析构函数
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};
int main()
{
	char* p1 = (char*)malloc(1021u * 1024u * 1024u * 2);
	printf("%p\n", p1);
}

运行结果如下:
我们可知:malloc失败返回值为NULL;
C++内存管理_第4张图片
我们来看看new失败后的返回值
C++内存管理_第5张图片
可知:显示处程序崩溃。
二:new失败是抛异常
当我们使用new创建空间失败时,却与malloc有着极大的不同。
代码如下:

class Test
{
public:
	Test() //构造函数
		:_a(0)
	{
		cout << "构造函数" << endl;
	}
	~Test() //析构函数
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};
int main()
{
	try
	{
		char* p2 = new char[1024u * 1024u * 1024u * 2 - 1];
		printf("%p\n", p2);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

运行结果如下:
我们可知,当new创建空间失败时,new会执行抛异常机制,即创建空间打失败立即跳转至catch代码处,执行catch代码。
C++内存管理_第6张图片

operator new和operator delete函数

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

int* p1 = (int*)operator new(sizeof(int) * 10);
	operator delete(p1);

等价于;


	int* p2 = (int*)operator new(sizeof(int) * 10);
	free(p2);

运行结果如下:
实际上,operator new 的底层是通过调用malloc函数来申请空间的,delete的底层是通过调用delete函数来申请空间的。
C++内存管理_第7张图片
总结:
使用new创建空间时,相当于使用了operator new函数 和 类的构造函数,而operator new函数实际是调用了malloc函数,当operator new函数调用失败则会调用抛异常机制。使用delete
删除空间,实际调用了operator delete和类的析构函数,而operator delete函数的底层是使用free函数实现的

C++内存管理_第8张图片

new和delete的实现原理

自定义类型

new的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]的原理
1:. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
4. 在申请的空间上执行N次构造函数
5.
delete[]的原理
1:. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2:. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

定位new和表达式(placement-new)

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

使用格式: new( place_adress) type ,其中place_adress 必须是一个指向变量的指针

using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));  
	//p1现在指向的打不过是与A对象相同的大小的一段空间,还不能算是一个对象,因为构造函数没有执行。
	new(p1)A(10);//注意:如果打A类的构造函数有参数时,此处需要传参。
	p1->~A();   //定为new不会调用析构函数。
	free(p1);
	return 0;
}

常见面试题

malloc / free和new /deelete的区别

相同点:
 都是从堆上申请空间,并且需要用户手动释放。

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

你可能感兴趣的:(C++,c++)