1.内存分配

在C/C++中计算机的主要内存被分为栈区,堆,静态区,代码区。

栈区,栈区通常是用于函数建立栈帧,而栈帧中又储存局部变量,返回地址,函数参数等信息;遵循后进先出的原则,栈区是由低地址方向增长,也就是自下生长。

栈区运行效率高,但分配的内存少,容易发生栈溢出等问题。

它的速度很快,并且因为系统给栈区所分配的空间很少,所以内存空间不够时就会导致栈溢出。

堆,在内存中占有很大的空间,是我们进行动态内存申请的地方,在C中我们使用malloc,realloc,calloc等函数进行空间申请,再用free释放;C++使用new申请空间,delete释放。注意在申请完资源时要及时的释放资源避免造成内存泄漏。

堆是向上生长,朝着内存地址增加的方向生长。

静态区,也叫做数据段,存储全局变量以及静态变量的地方。

代码区,也叫做文本段,这里存放了编译指令和常量,如字符串常量等,这些具有只读性不可以任意修改。

2.new与delete

C语言中的malloc/free在C++中可以继续使用,但是在面向对象生命周期管理时却是有些无能为力了。

在C++中new/delete相比C语言中malloc/free有着不小的改进,如自动调用构造/析构。这种“内存管理+对象行为”的深度耦合行为,实现了内存管理与对象生命周期的绑定。

2.1对于内置类型

int main()
{
	//操控内置类型时

	//动态申请内存空间
	int* a = new int;
	int* b = new int(10);//直接初始化,初始化为10
	int* c = new int[10];//申请10个int类型的数组空间,默认初始化为0
	int* d = new int[10]();//申请10个int类型的数组空间,值初始化为0

	delete a;
	delete b;//释放动态申请的空间
	delete[] c;
	delete[] d;//释放动态申请的数组空间

	return 0;
}

2.2对于自定义类型

class date
{
public:
	date(int a=0):_a(a)
	{
		cout << "默认构造" << endl;
	}
	~date()
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};
int main()
{
	//操控自定义类型时
	date* p = new date(1);//申请空间并调用构造函数
	date* q = (date*)malloc(sizeof(date));//只是申请空间,并没有调用构造函数
	delete p;//先调用析构函数,再释放空间
	free(q);//直接释放空间,并没有调用析构函数
    q=nullptr;
	
	return 0;
}

另外,对于自定义类型时,使用new申请空间时,编译器通常会自动的在前面加上四个字节拿来存储空间的大小方便于delete[]操作,但是如果类中没有析构函数没有资源要释放,编译器就会优化这一操作,将存储大小的4个字节给省略。

当然对于内置类型的话就无所谓了。

3.operator new与operator delete

new与delete是用户进行动态内存申请与空间释放的操作符,而operator new与operator delete是系统所提供的全局函数,new在底层调用operator new来进行空间申请,delete则是在底层调用operator delete来进行空间释放。

而operator new实际上是通过malloc来进行空间申请,当malloc空间申请成功则会直接返回,申请失败则会尝试执行空间不足措施。

如果用户提供具体措施则会继续申请,若没有则会抛异常,operator new则会抛出std::bad_alloc这时我们可以使用try-catch来捕捉异常

class date
{
public:
	date(int a=0):_a(a)
	{
		cout << "默认构造" << endl;
	}
	~date()
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};
int main()
{
	try
	{
		date* p = new date[1000000];
		//可能会抛出异常bad_alloc

		delete[] p;
		p = nullptr;
		cout << "内存分配与释放成功" << endl;
	}
	catch (const bad_alloc& e)
	{
		cerr << e.what() << endl;
		//cerr是用来输出错误信息的流对象
	}
	catch (...)
	{
		cerr << "未知异常" << endl;
		//捕获其他异常
	}
	
	return 0;
}

当然如果你不想用new但想要手动的实现new也是可以的。

毕竟开头就说了,operator new与operator delete是全局函数是可以直接使用的,并且它们的底层就是malloc和free,不需要进行判断为空,会抛异常。

class a
{
public:
	a(int n = 0) :_a(n)
	{
		cout << "默认构造" << endl;
	}
	~a()
	{
		cout << "析构函数" << endl;
	}
private:
	int _a;
};
int main()
{
	a* a1 = new a(10);

	a* a2 = (a*)operator new(sizeof(a));//只是申请空间,并没有调用构造函数,相当于malloc
	//调用构造函数,不能显式调用构造
	new(a2) a(10);//定位new,在a2的空间上构造一个a对象

	//析构却可以显式调用
	a2->~a();
	delete a1;

	operator delete(a2);//仅释放a2的空间,不调用析构,等价于free

	a1 = nullptr;
	a2 = nullptr;


	return 0;
}

既然只需要new和delete就可以完成内存空间的申请与释放,那么为什么还要将operator new与operator delete设计成全局函数呢?

当然是为了自定义内存管理如为了内存池等提供了底层接口。

内存池中的核心是池化技术,就是预先从系统申请一大块内存进行集中管理,提供未初始化的原始内存块供复用,避免高度频繁申请。

那么这时我们向内存池申请空间时,又由于new是申请空间后自动进行初始化,而现在是向内存池,这种已有空间申请却又没有初始化。

这时我们想要在这块已有内存上完成对象初始化,就只能用定位new了。

4.malloc/free与new/delete的区别

对于内置类型,malloc/free与new/delete基本类似,只不过的是new与delete是针对于单个元素的空间,而new[]与delete[]是针对于连续的空间,而且new申请失败会抛异常,malloc则是返回空指针nullptr。

new[]的原理,先调用operator new[],在operator new[]中实际上是调用operator new完成对n个对象空间的申请,再调用n次构造函数。

delete[]的原理,先调用operator delete[],在operator delete[]中实际上是调用operator delete完成对n个对象空间的释放,再调用n此析构函数完成资源的清洗。

malloc的返回类型是void*使用时必须强转,而new则是不需要。

malloc申请空间需要判断空,而new则是需要捕获异常。

malloc/free是函数,new/delete则是操作符。

更多推荐