关于C++的内存管理, 以及C++当中的 operator new和operator delete函数, 定位new表达式

在C语言当中, 动态内存的管理方式分为 malloc / calloc / realloc 和 free

  1. malloc函数可以在堆上申请指定字节的内存空间, 申请成功返回申请到的空间的首地址, 如果申请失败会返回NULL.

    void* malloc(size_t size);
    注意malloc申请到的空间是没有经过初始化的

  2. calloc函数与malloc的功能相似, 但是calloc申请到的空间是经过初始化的, 初始化内容为0.

     void* malloc(size_t, num, size_t size);//申请num个字节大小为size的空间
    
  3. realloc函数

     void* realloc (void* ptr, size_t size);
    

    realloc函数会改变ptr所指向空间的内存大小, 其中ptr必须是指向堆内存空间的指针, 也就是由malloc/calloc/realloc函数所分配空间的指针. 如果size小于或等于之前ptr所指向空间的大小, 就会保持原来状态不变, 如果size大于之前ptr所指向空间的大小, 会重新开辟一块大小为size的内存空间, 并将原来所指向空间中的内容复制到新的空间上. realloc函数所申请的空间也是没有经过初始化的.

C语言内存管理方式在C++当中仍然可以继续使用, 但有些地方用起来并不方便, 比如开辟自定义类型的空间, 因此在C++当中又提出了自己的诶存管理方式, 通过new和delete操作符进行动态内存管理.

new / delete对于内置类型的操作

内置类型就是 int, char, float, double等

void test()
{
	//动态申请一个int类型的空间
	int* pi = new int;//申请一个4字节整型空间

	//动态申请一个int类型的空间并初始化
	int* pi2 = new int(2);//申请一个4字节整型空间并赋值为2

	//动态申请int类型的数组
	int* pi3 = new int[5];//申请了一个长度为5的整型数组

	delete pi;
	delete pi2;
	delete[] pi3;
}

注意:

  1. 注意申请空间时 () 和 [] 的区别, ()是将申请到的空间初始化, 而 [] 是申请数组时用的;

  2. 对应的释放空间时, 释放连续空间(即数组)时记得要用到 delete[].

  3. 连续使用不被允许, 做不到申请一片连续空间并初始化里面的值

     int* pi4 = new int[5](1);//这样写是不可以的
    

在这里插入图片描述
其实看到这里, 我们就会想, 在C语言当中已经有了 malloc / calloc / realloc / free这些函数, 那为什么在C++当中又要出现 new / delete, new [] / delete[]?

 首先, 针对内置类型来说, 无论使用 new 还是 malloc 等其实都是一样的.
 对于自定义类型来说, 就会不一样了. 比如下面的例子:

自定义类型 A

class A
{
public:
	A(int x = 10) :
		_val(x)
	{
		cout << "A()" << endl;
	}

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

用malloc申请空间

void test2()
{
	A* pA_C = (A*)malloc(sizeof(A));
	cout << pA_C << endl;
	cout << pA_C->getVal() << endl;
	free(pA_C);
}

输出结果:

在这里插入图片描述
空间申请成功, 类成员无法初始化打印随机值.
接下来使用 new / delete

void test1()
{
	A* pA_CPP = new A;//申请单个A类型的对象
	cout << pA_CPP << endl;
	cout << pA_CPP->getVal() << endl;
	delete pA_CPP;//释放掉这片空间
}

输出结果:
在这里插入图片描述
可以很清楚的看到, new在针对自定义类型申请空间的时候, 会自动调用构造函数, 构造函数又帮我们完成了对类成员_val的初始化, 而delete会自动调用析构函数完成资源的清理工作.

我们可以进一步验证一下, new / delete 针对自定义类型的操作, 接下来我们申请一个A类型的对象数组

// new / delete
A* pA_CPP_ARR = new A[10];
delete[] pA_CPP_ARR;

在这里插入图片描述
调用10次构造函数, 10次析构函数

// malloc
A* pA_C_ARR = (A*)malloc(sizeof(A)* 10);
free(pA_C_ARR);

在这里插入图片描述
malloc只开辟空间.

结论: 建议在C++当中使用 new / delete

operator new和operator delete函数

看到operator我们一定会想到运算符重载, 某种角度来说这样理解也可以, 但是这里我们要尽量将其看做一个整体, 看做函数来理解它.

同样, 先给一个自定义类型

class A
{
public:
	A(int x = 10) :
		_val(x)
	{
		cout << "A()" << endl;
	}

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

	int getVal()
	{
		return _val;
	}
private:
	int _val;
};

接下来申请空间

void test1()
{
	A* ptr1 = (A*)malloc(sizeof(A));
	A* ptr2 = new A(1);
	A* ptr3 = (A*)operator new(sizeof(A));


	free(ptr1);
	delete ptr2;
	operator delete(ptr3);
}

执行结果

在这里插入图片描述
均申请空间成功, 通过上面的讲解我们知道, new / delete 会调用构造和析构, 那么operator new / operator delete 又是干什么用的呢?它与malloc / free的区别在哪里呢?

  1. 首先operator new / operator delete的使用与 malloc / free相同

     void* operator new(size_t size);
     operator delete(void* ptr);
    
  2. 举个例子来看operator new与malloc的区别

     //用malloc去申请空间
     size_t size = 2;
    
     void* ptr1 = malloc(size * 1024 * 1024 * 1024);
     cout << ptr1 << endl;//申请失败, 返回NULL(0)
    

在这里插入图片描述

	//用operator new去申请空间
	void* ptr2 = operator new(size * 1024 * 1024 * 1024);
	cout << ptr2 << endl;

在这里插入图片描述
程序会崩掉. 因为operator new对比malloc, operator new申请空间失败会抛异常(面向对象的错误处理方式), 必须跳到跳到catch的地方, 所以代码要修改为下面这样

try
{
	void* ptr2 = operator new(size * 1024 * 1024 * 1024);
	cout << ptr2 << endl;
}
catch (exception& e)
{
	cout << e.what() << endl;
}

运行结果如图:
在这里插入图片描述
总结: operator new 对比 malloc 使用方式相同, 处理错误的方式不同

new本身就是为了C++面向对象编程而产生的, 因此从malloc->operator new->new是层层递进的

malloc
operator new ==> malloc + 申请空间失败抛异常的实现
new          ==> operator new + 自动调用构造函数

对应的择优free, operator delete, delete
而delete 比起 free 不一样的地方就是 delete 自调用析构函数清理资源.
operator delete 与 free没有区别.因为释放空间失败会自动终止程序.

new / delete原理

new原理

  1. 调用 operator new函数申请空间
  2. 在申请到的空间上执行自定义类型的构造函数, 完成对象的构造

delete原理

  1. 在申请到的空间上执行自定义类型的析构函数, 完成资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]原理

  1. 实际调用operator new函数申请N个对象的空间
  2. 在申请到的空间执行N次自定义类型的构造函数

delete[N]原理

  1. 在申请到的空间上执行N次自定义类型的析构函数, 完成N个对象的资源清理
  2. 实际调用 operator delete函数释放空间

定位new表达式

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

比如:

class Test
{
public:
	Test(int x = 10) :
		_a(x)
	{
		cout << "Test" << endl;
	}

	int GetVal()
	{
		return _a;
	}

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

private:
	int _a;
};

void test3()
{
	Test* pT = (Test*)malloc(sizeof(Test));//申请好的内存

	new(pT) Test(10);//定位new表达式

	cout << pT->GetVal() << endl;
}

总结

malloc / free和new / delete的区别

  1. malloc / free是函数, new / delete是操作符
  2. malloc申请空间不会初始化, new可以(自动调用构造函数)
  3. malloc函数使用时需要手动计算类型大小(sizeof(T)), new不需要, 直接在new后面跟所需空间类型即可
  4. malloc函数申请空间返回值为void*, 因此使用时要做类型强转, new不需要
  5. malloc申请失败返回NULL(0), new申请失败抛异常
  6. 最重要的针对自定义类型, malloc / free只申请和释放空间, new会自动调用构造函数完成对象的初始化, delete会调用自定义类型的析构函数完成资源清理, 再释放空间.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章