内存管理

147 阅读3分钟

回顾C语言内存管理

C语言对内存申请的方式有:malloc,calloc,realloc

那么这三个之间的区别是什么?

  1. malloc只负责申请空间,不初始化。
  2. calloc申请空间同时初始化。
  3. realloc是对已有空间的扩容,有可能原地扩容,也有可能异地扩容。
    • 如果是原地扩容,那么realloc的返回值应当和calloc的返回值一样。
    • 如果是异地扩容,系统会自动释放calloc的返回值,并且给realloc重新返回值。

C++内存管理

C++兼容C语言,因此满足C语言对内存的管理。

但是自己也有一套管理内存的方式:newdelete

两者对比

  • 对内置类型
int main()
{
	// 申请一个int型空间
	// C
	int* p1 = (int*)malloc(sizeof(int));
	// C++
	int* pp1 = new int;

	// 申请10个int型空间
	// C
	int* p2 = (int*)malloc(sizeof(int) * 10);
	//C++
	int* pp2 = new int[10];
	return 0;
}

image-20240220210038823

可以看到,newmalloc对内置类型都是一样的,只申请空间,但不初始化。

int main()
{
	// 申请一个int
	int* p1 = new int;
	int* p2 = (int*)malloc(sizeof(int));

	// 申请一个int并初始化为1
	int* p3 = new int(1);

	// 申请10个int
	int* p4 = new int[10];

	// 申请10个int并初始化
	int* p5 = new int[10]{ 1,2,3,4,5 };

	return 0;
}

image-20240221162100433

  • 对自定义类型
class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};


int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	return 0;
}

image-20240220211353172

image-20240220211511222

image-20240220211635416

image-20240220211720686

image-20240220211953451

可以看到:

  1. 操作符new会调用构造函数,delete会调用析构函数。
  2. new会申请空间并初始化,而malloc仅仅是开空间。

尤其在如下场景:

struct ListNode
{
	int _val;
	ListNode* _next;

	ListNode(int val)
		:_val(val)
		,_next(nullptr)
	{}
};

int main()
{
	ListNode* n0 = (ListNode*)malloc(sizeof(ListNode));


	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	return 0;
}


image-20240220212843639

可以看到,对自定义类型而言,使用new比使用malloc更加简单方便。

new和delete的注意事项

  • 对自定义类型new多次和delete多次
class A
{
public:
	A()
		:_a(0)
	{
		cout << this << "  A()" << endl;
	}


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

int main()
{
	// 先调用operator new --> 封装malloc
	// 然后调用构造函数
	//A* p = new A;

	// 先调用析构函数,在释放资源
    // operator delete -> free
	//delete p;

	// 调用operator new[] --> operator new --> 封装malloc
	// 调用5次构造函数
	A* p1 = new A[5];

	cout << "p1的地址: " << p1 << endl;
	// 释放5次析构函数
	delete[] p1;

	return 0;
}

image-20240221170203902

==new几个就调用几次构造,delete也是一样。==

freenew搭配使用

  • 情况一正常

    少调用析构函数,没问题。

class A
{
public:
	A()
		:_a(0)
	{
		cout << this << ":  A()" << endl;
	}

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

private:
	int _a;
};

int main()
{
	// new内置类型的时候,没有构造函数,也没有析构函数。
	int* p1 = new int[5];
	// 调operator delete内部也是调free
	// 这里调用operator delete的时候没有析构函数和直接掉free没区别
	free(p1);

	A* p2 = new A;
	// 这里调free只是没有调用析构函数,但是A类中也没有资源要释放。也不影响
	free(p2); // 少调用了析构函数

	return 0;
}

image-20240221183211969

  • 情况二不正常

    少调用析构函数,导致内存泄漏,但是不会报错。。

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
		_a = new int[4];
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	// st1会调用构造函数,st2会调用吗?
	// 不会,因为内置类型不会调用构造函数,自定义类型才会
	Stack st1;
 	Stack* st2 = new Stack;
	delete st2;
	//free(st2);

	return 0;
}

image-20240221191605159

image-20240221191829338

image-20240221191903212

所以,自定义类型中如果有资源释放,用free可能会造成内存泄漏。。因此,最好不要混合使用