C++内存管理

专栏:C/C++
个人主页:HaiFan.
专栏简介:本章为大家带来C++的内存管理方式。

内存管理

  • C/C++内存分布
  • C语言中的动态内存管理方式
  • C++内存管理方式
    • new和delete操作内置类型
    • new和delete操作自定义类型
  • 定位new表达式(placement-new) (了解)
  • 内存泄漏

C/C++内存分布

  1. 栈(Stack):栈用于存放函数的局部变量,以及函数调用时的一些上下文信息,例如寄存器值、返回地址等。栈是一块连续的内存区域,在函数调用时自动分配,函数返回时自动释放。栈的大小有限,超出栈的容量会导致栈溢出。
  2. 堆(Heap):堆用于动态地分配内存,例如通过new、malloc等关键字进行申请。堆的大小不受限制,但需要程序员手动管理内存的分配和释放,否则会导致内存泄漏。
  3. 全局区(Global):全局区用于存放全局变量和静态变量,它们在程序运行期间整个生命周期内都存在,并且在程序启动时就被分配了内存,程序结束时才被释放。
  4. 常量区(Const):常量区用于存放常量数据,例如字符串常量等。常量区的数据不可修改,不能通过指针修改。
  5. 代码区(Code):代码区用于存放程序的指令和函数代码,它也是只读的,不能修改。

当程序运行的时候,操作系统会为其分配一块内存,该内存被分为以下几个部分:

  1. 栈:非静态局部变量/函数参数/返回值等等
  2. 堆:用于程序运行时动态内存分配
  3. 数据段:存储全局数据和静态数据
  4. 代码段:可执行的代码/只读常量

C语言中的动态内存管理方式

C语言中的动态内存管理方式是: malloc,realloc,calloc,free,相信这四个函数大家应该不陌生,在数据结构部分,经常用这几个函数去动态开辟空间。

int main()
{
	int* a = (int*)malloc(sizeof(int));
	if (a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int* tmp = (int*)realloc(a, sizeof(int) * 4);
	if (tmp == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}
	a = tmp;

	int* b = (int*)calloc(0, sizeof(int));
    free(a);
    a = NULL;
    free(b);
    b = NULL;
	return 0;
}

简单复习一下这几个函数。malloc用于申请一块空间,realloc用于给当前的空间扩容(异地扩容或者原地扩容),calloc用于申请一块空间并初始化。

这是C语言的内存管理方式。

C++内存管理方式

new和delete操作内置类型

C语言的内存管理方式可以在C++中继续使用,但是用起来比较麻烦,所以C++提出了自己的内存管理方式-----------new和delete操作符进行内存管理。

  • new:用于动态地分配内存,返回一个指向已分配内存的指针
  • delete:用于释放动态分配的内存,释放以指针指向的内存块。
  • new[]:用于分配一个数组的内存空间,返回一个指向数组第一个元素的指针。
  • delete[]:用于释放动态分配的数组内存空间,释放内存空间中的所有元素。
int main()
{
	int* a = new int;

	int* a1 = new int(1);

	delete a;
	delete a1;

	return 0;
}

a和a1的区别是什么?

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

从监视窗口中可以看出,a1被初始化了,而a没有。new int是动态申请一个int类型的空间,new int(1)是动态申请一个int类型的空间并初始化。


int main()
{
	int* a = new int[3];

	int* a1 = new int[3]{ 1,2,3 };

	delete[] a;
	delete[] a1;

	return 0;
}

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

第一个不会给数组初始化,而第二个会给数组初始化,对于使用 new 运算符分配的动态数组,如果不进行完全初始化,则剩余的元素将默认为 0。

如果想让数组全为0,则可以

int* a1 = new int[5]();

new和delete操作自定义类型

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

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

private:
	int _a;
};

int main()
{
	A* a1 = new A(1);
	delete a1;

	A* a2 = (A*)malloc(sizeof(A));
	free(a2);
	return 0;
}

输出结果是 A(int a) ~A()

从结果中可以看出,new/delete和malloc/free最大区别是 前者对于自定义类型除了开空间,还会调用构造函数和析构函数。

定位new表达式(placement-new) (了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

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

内存泄漏

内存泄漏是指分配的堆内存没有被释放或被错误的方式释放,导致应用程序无法再次访问这些内存。由于内存泄漏占用了大量的系统内存资源,因此它会损害应用程序的性能和可靠性,并可能导致应用程序最终崩溃。

忘记delete内存。忘记释放动态内存会导致内存泄漏问题,因为这种内存不可能被归还给自由空间了。查找内存泄漏错误是非常困难的,因为通常应用程序运行很长时间后,真正耗尽内存时,才能检测到这种错误。

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