【C++入门】C++内存管理

目录

前言

 C/C++内存分布

 C++内存管理方式

1. new和delete操作内置类型

快速了解与使用

 2. new和delete操作自定义类型

3. operator new与operator delete

 4. operator new [ ] 

 *5.定位new

 6. malloc/free和new/delete的区别

总结


前言

         C++作为一种面向对象的编程语言,继承了C语言的内存管理特性,同时也引入了更加灵活和高级的内存管理机制。在C++中,内存管理涉及到动态内存分配、内存释放、内存泄漏等问题,对于程序的性能和稳定性都有着重要的影响。本文将详细介绍C++中的内存管理。

在这里插入图片描述

 C/C++内存分布

我们先来了解一下C/C++中内存区域的划分:

【C++入门】C++内存管理_第1张图片

:栈是用来存放局部变量和函数调用信息的地方。当一个函数被调用时,它的参数和局部变量会被压入栈中,当函数执行完毕时,这些数据会被自动弹出。栈的大小是有限的,通常在几兆字节到几十兆字节之间

:堆是用来存放动态分配的内存的地方。在堆中分配的内存需要手动释放,否则会导致内存泄漏。堆的大小通常受系统总内存的限制,可以动态扩展

数据段:这个区域用来存放全局变量和静态变量。全局变量在程序整个运行周期内都存在,而静态变量在它们所在的函数执行期间存在,但是在程序整个运行周期内都存在

代码段:这个区域存放程序的代码和常量数据,如字符串常量。这部分内存通常是只读的

 目前的学习了解这几个即可。

 C++内存管理方式

1. new和delete操作内置类型

        C++继承了C语言的内存管理特性,C语言内存管理方式在C++中可以继续使用,但在C++一些使用场景下C的内存管理使用时又有些比较麻烦。于是C++引入了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

快速了解与使用

 使用示例:

int main()
{
	// 动态申请一个int类型的空间
	int* p1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* p2 = new int(10);
	// 动态申请10个int类型的空间
	int* p3 = new int[3];
	delete p1;
	delete p2;
	delete[] p3;
}
  • new和delete要配合使用

 不可malloc开空间,delete释放或者new开空间,free释放

  •  new和delete使用时类型要相符

比如:使用 new 开空间,delete [ ] 释放,new  [ ] 开空间,delete 释放 (不可)

  • new 和delete申请和释放空间必须是完整连续的

 与C语言中malloc和free类似,使用new来申请一块内存空间,那么必须使用delete来释放整块内存

 2. new和delete操作自定义类型

 以一个简单的栈为例:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;

		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int  _top;
	int  _capacity;
};



int main()
{
	Stack* p1 = new Stack;
	delete p1;
}

 new/delete对于  自定义类型  除了开空间还会调用构造函数和析构函数

Stack* p1 = new Stack; //主要干两件事:开空间 + 调构造

【C++入门】C++内存管理_第2张图片

  1.  先给Stack 指针 p1开块空间
  2.  调用构造函数给p指向的空间进行初始化
delete p1; // 析构 + 释放空间

【C++入门】C++内存管理_第3张图片

  1.  先释放_a的空间(先释放p1空间会导致_a指向空间丢失)
  2. 释放p1指向的空间

3. operator new与operator delete

 看到operator或许你已经有了猜想,new和delete其实就是操作符

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

Stack* p1 = new Stack;
delete p1;


Stack* p2 = (Stack*)operator new(sizeof(Stack));
operator delete(p2);

 在调用时new 和 operator new(delete 和 operator delete)其实是不一样的,通过两种方式的输出结果就可以看出来。

只有new 和 delete 调用了 构造 和 析构。

operator new 并不具备初始化的作用,它存在的意义:new对自定义类型是开空间 和 调构造,而operator new用于new的开空间操作

 operator delete 只有释放空间的作用,并不会调用析构函数 他的意义是为了与new 配对。

 4. operator new [ ] 

 开单个对象的空间我们了解了,那开多个对象的空间是怎么开的?

  1.  一次申请10个Stack对象的空间
  2.  调用10次构造函数(给每个对象的_a开空间)

【C++入门】C++内存管理_第4张图片

 operator new [ ] 的底层其实调用的是operator new,实际调用的关系:

 operator new [ ] --> operator new --> malloc

operator new [ ]在函数内计算出要开的字节大小,然后使用operator new 去开空间:

【C++入门】C++内存管理_第5张图片

 32位环境下,开10个stack对象的空间,size应该是120,可为什么是124?

这多出的4个字节开在开头位置:

 【C++入门】C++内存管理_第6张图片

 这多开出的4个字节空间存储的其实是new [ ]中的值(申请Stack对象的个数)。

        这个空间的数据就是申请空间Stack对象的个数,例子中我开10个Stack对象空间,所以存储的是10.

        这个10实际上是为delete [ ]  p3 准备的,在调用 delete [ ]时调用10次析构函数,释放每个对象_a的空间;

delete [ ] 释放空间时会连同这4个字节一起释放(让指针回到new [ ]所开空间最开始的位置)这也就是为什么建议使用 new 和 delete 时配合使用。

因为使用new [ ] 开空间,一旦自定义类型的对象涉及到内存申请,使用delete就会导致释放的空间不完整(delete会导致最开始的4字节空间不被释放),程序就会挂掉。

 建议: new / delete、new [ ] / delete [ ]、malloc / free 一定要配对使用

 *5.定位new

 在了解定位new之前,先思考一个问题,构造函数能不能显示调用?

 答案是不能,下边是测试的代码,可以自己测试一下。

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*)operator new(sizeof(A));
	//p1->~A(1);
	return 0;
}

 虽然构造函数没法正常的进行显示调用,但是我们可以使用定位new来显示调用构造函数

new(p1)A; //new(p1)A(1);

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

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列

 构造函数不能直接显示调用,析构函数可以直接显示调用

p1->~A(); //调用析构
operator delete(p1); // 释放空间

 总的来说:

// new 效果
A* p1 = (A*)operator new(sizeof(A));
new(p1)A;

// delete 效果
p1->~A();
operator delete(p1); 

 应用场景:

那定位new有什么用?直接用 new 和 delete 不是更简洁

 定位new表达式在实际中一般是配合内存池使用

 在一些应用场景中,可能涉及到频繁的new申请空间,这样效率很低,为了解决效率问题,就有了池化技术——内存池

频繁的申请空间麻烦,那就一次多申请一些空间,使用时调用构造函数进行初始化

 这时调用构造函数就需要使用定位new。

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


总结

         内存管理是C++编程中一个重要且复杂的主题,合理地使用new和delete可以避免内存泄漏和提高程序的性能。同时,了解定位new的使用场景也能让我们更好地控制内存分配和对象构造的过程。以上便是本文全部内容,希望对你有所帮助,感谢阅读!

你可能感兴趣的:(c++,算法,开发语言)