「C++」内存管理

时间:2024-10-20 08:16:44

目录

前言

C语言中的内存管理方式

C++内存管理方式

new/delete操作内置类型:

new/delete操作自定义类型:

new申请空间同时初始化

new创建链表

new多参数传参

new失败的抛异常

位数与指针关系

operator new与operator delete函数

new和delete的实现原理

1. 内置类型

2. 自定义类型

定位new表达式(palcement-new)

后记


前言

欢迎大家再次来到小鸥的C++频道~

今天我们将讲解C++中有关内存管理的内容~

C语言中的内存管理方式

malloc/calloc/realloc/free

在我们学习C语言的时候我们知道,C语言中就是靠上述的四个函数进行动态内存管理的

下面有一段简短的代码,可以简单的回忆一次其相关内容

void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

由C语言知识我们知道,realloc是对p2进行扩展,而扩展分为两种情况:若为本地扩展,那么扩展成功后p3就是p2;若为异地扩展,那么realloc会自动释放p2指针。

C++内存管理方式

虽然C++也兼容C语言的内存管理方式,但具有一定的局限性,且使用较为麻烦,所以C++提供了自己的内存管理方式:使用两个新的操作符new和delete进行动态内存管理。

new/delete操作内置类型:

注:申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[]和delete[],要匹配起来使用。

new/delete操作自定义类型:

class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << "Print->" << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = new A;
	A* p1 = new A[3];

	delete p;
	delete[] p1;
	return 0;
}

结果如下:

从打印的结果可以推出:new和delete操作符不仅会申请和释放空间,并且会自动调用A类的默认构造函数和析构函数,直接就创建出了对象。

由此可见:

new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数。

new申请空间同时初始化

	//申请空间时允许传参进行初始化
	A* p = new A(1);
	A* p1 = new A[3]{ 1, 2, 3 };
	p->Print();
	for (int i = 0;i < 3; i++)
	{
		(p1 + i)->Print();
	}
	delete p;
	delete[] p1;

new创建链表

typedef int LNDataType;
struct ListNode
{
	LNDataType _val;
	ListNode* _next;

	//构造函数,new操作符直接自动调用,直接替代了C语言实现链表中用于获取节点的BuyNode函数
	ListNode(LNDataType x)
		:_val(x)
		,_next(nullptr)
	{}
};

int main()
{
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);
	n1->_next = n2;
	n2->_next = n3;
	n3->_next = n4;
	return 0;
}

new多参数传参

//多参数类
class A
{
public:
	A(int a = 1,int b = 1)
		:_a(a)
		,_b(b)
	{
		cout << "A(int a = 1,int b = 1)" << endl;
	}
	A(const A& a)
		:_a(a._a)
		,_b(a._b)
	{
		cout << "A(const A& a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << "Print->" << _a << "/" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
	A* p = new A(1, 2);

	//本质是隐式类型转换
	//A aa = { 1, 1 };
	A* p1 = new A[3]{ {1,2}, {2,3}, {3,4} };

	//使用匿名对象进行初始化
	A* p2 = new A[3]{ A(1,2), A(2,3), A(3,4) };

	A aa1 = (1, 1);
	A aa2 = (2, 2);
	A aa3 = (3, 3);
	//有名对象进行初始化,调用拷贝构造
	A* p3 = new A[3]{ aa1, aa2, aa3 };

	p->Print();
	for (int i = 0; i < 3; i++)
	{
		(p1 + i)->Print();
	}
	delete p;
	delete[] p1;
	delete[] p2;
	delete[] p3;
	return 0;
}

new失败的抛异常

(实际上一般都是不会失败)

new操作符在申请空间失败后,不会像malloc一样返回空指针,而是有一个叫做”抛异常“的概念;由throw try/catch组成

	//1M 约等于 100W Byte
	//1G 约等于 10亿 Byte
	void* p1 = new char[1024 * 1024 * 1024];
	cout << p1 << endl;
	void* p2 = new char[1024 * 1024 * 1024];
	cout << p2 << endl;
	return 0;

如果不进行捕获异常,程序就会直接报错。

使用try catch捕获异常:

在32位环境下,按照1G为单位申请空间

int main()
{
	//1M 约等于 100W Byte
	//1G 约等于 10亿 Byte
	try
	{
		void* p1 = new char[1024 * 1024 * 1024];
		cout << p1 << endl;
		void* p2 = new char[1024 * 1024 * 1024];
		cout << p2 << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

在64位环境下按照同样的1G为单位申请空间

int main()
{
	try
	{
		int n = 1;
		while(1)
		{
			void* p1 = new char[1024 * 1024 * 1024];
			cout << n++ << endl;
		}

	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

try catch捕获异常,e.what() 可以返回异常原因。

位数与指针关系

由上文64位环境下的申请的空间可以知道,空间申请是先申请的虚拟内存(不然我的电脑就爆炸啦!),详细的可以自行搜索学习(我也不知道~)

operator new与operator delete函数

上文中我们讲解了new和delete操作符,他们是用户进行动态内存申请和释放的操作符;而operator new和operator delete是系统默认提供的两个函数,new和delete操作符的底层就是调用这两个函数来进行空间的申请和释放的。


/*operator new:该函数实际通过ma110c来申请空间,
当ma11oc申请空间成功时直接返回:申请空间
失败,尝试执行空
间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。*/
void *__CRTDEcL operator new(size_t size)_THROWl(_STD bad_alloc)
{
    // try to allocate size bytes
    void *p;
    while((p = ma11oc(size)) == 0)
        if(_ca1lnewh(size) == 0)
        {
            // report no memory
            // 如果申请内存失败了,这里会抛出bad_a11oc 类型异常
            static const std::bad_alloc nomem;
            _RAISE(nomem);
        }
    return (p);
}
/*
operator delete:该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
    _CrtMemBlockHeader *pHead;
    RTCCALLBACK(_RTC_Free_hook,(puserpata,0));

    if(puserData == NULL)
        return;

    _mlock(_HEAP_LOCK); /* block other threads */
    __TRY

        /* get a pointer to memory block header */
        pHead = pHdr(puserData);
        /* verify block type */
        _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockuse));
        _free_dbg( puserData, pHead->nBlockuse );
    __FINALLY
        _munlock(_HEAP_LOCK);/*release other threads */
    __END_TRY_FINALLY

    return;
}
/*
free的实现
*/
#define  free(p)  _free_dbg(p,_NORMAL_BLOCK)

由上述简单观察就可以看出,operator new和operator delete实际上也是通过使用malloc和free来实现其功能的,即new和delete的本质就是包装起来的,跟高级好用的malloc和free

new和delete的实现原理

1. 内置类型

对于内置类型来说,new与malloc,delete与free基本类似;而new与delete用于申请释放单个元素空间,new[]和delete[]用于申请释放连续的空间,且new在申请失败时会抛异常,malloc则会返回NULL指针。

2. 自定义类型

new的原理:1.先调用operator new函数申请空间;2.在申请的空间上自动调用自定义类型的构造函数,完成对对象的构造。

因此new出来的对象就已经是构造完成的。

new[]的原理:1.调用operator new[]函数,在operator new[]中调用operator new函数完成N个对象空间的申请;2.在申请的空间上执行N次构造函数完成对象的构造。

delete的原理:1.先调用对象的析构函数,完成对象中资源的清理工作;2.调用operator delete函数,释放对象空间;

delete[]的原理:1.先执行N次析构函数,完成对N个对象的资源清理;2.调用operator delete[],实际也是调用operator delete函数来释放N个对象的空间。

定位new表达式(palcement-new)

定位new表达式是用来给已经申请了空间而没有进行构造的对象空间使用的

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

实际效果和直接使用new是一样的,大多是情况使用不到

使用场景:

配合内存池来使用,内存池分配出的内存是没有进行初始化的,所以如果是自定义类型的对象,就需要使用定位new的定义式来显示调用构造函数(构造函数一般不能显示调用)

后记

再次感谢各位读者的阅读~

本篇还有不足的地方欢迎大家在评论区指出,本篇没有详细解释的部分名词大家可以自行搜索学习~

本期专栏:C++_海盗猫鸥的博客-****博客

个人主页:海盗猫鸥-****博客

感谢各位的关注~