【C++】内存管理

目录

  • C++内存管理方式:
    • new/delete操作内置类型:
    • new和delete操作自定义类型:
  • new与delete的底层:
    • operator new与operator delete函数:
  • 定位new:
    • 语法与使用方式:
  • 关于出现对内存有泄露隐患或报错的情况:

C++内存管理方式:

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

new/delete操作内置类型:

void Test1()
{
	// 动态申请一个int类型的空间
	int* ptr1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);
	// 动态申请10个int类型的空间
	int* ptr3 = new int[3];
	// 动态申请5个int类型初始化的空间
	int* ptr4 = new int[5]{ 1,2,3 };

	delete ptr1;
	delete ptr2;
	delete[] ptr3;
	delete[] ptr4;
}

【C++】内存管理_第1张图片
注意

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

new和delete操作自定义类型:

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

void Test2()
{
class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void Test2()
{
	// 动态申请一个A类型的空间
	A* p1 = new A;
	// 动态申请一个A类型的空间并初始化
	A* p2 = new A(1);
	// 动态申请两个A类型的空间
	A* p3 = new A[2];
	// 动态申请两个A类型的空间并初始化
	A* p4 = new A[2]{ 1 };

	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;
}

注意

在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

new与delete的底层:

讲完了new与delete的用法,现在可以了解一下实现他们的原理

operator new与operator delete函数:

看到operator与操作符的结合自然而然就会想到操作符重载,但是结果并不是。

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

现在看一下operator new与operator delete的实现代码:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空        间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 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实现得,随后可以得到这样一个层次图【C++】内存管理_第2张图片


new和delete在自定义类型下的实现原理

new的原理

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

delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]的原理

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

delete[]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

定位new:

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

已分配的原始内存空间指的就是内存池。
内存池存在的原因:
C++程序默认的内存管理(new,delete,malloc,free)会频繁地在堆上分配和释放内存,导致性能的损失,产生大量的内存碎片,降低内存的利用率。默认的内存管理因为被设计的比较通用,所以在性能上并不能做到极致。

内存池的思想是,在真正使用内存之前,预先申请分配一定数量、大小预设的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存,当内存释放后就回归到内存块留作后续的复用,使得内存使用效率得到提升,一般也不会产生不可控制的内存碎片。

语法与使用方式:

new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

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

关于出现对内存有泄露隐患或报错的情况:

主要的原因是因为没有使用匹配的释放内存方式

案例一:
例如在面对栈类时:

class Stack
{
public:
	Stack()
		:_a(new int[4])
		,_top(0)
		,_capacity(0)
	{}
	~Stack()
	{
		if (_a)
		{
			delete _a;
			_a = nullptr;
			_capacity = _top;
		}
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

void test()
{
	Stack st;
	Stack* pst = new Stack;

	free pst;
}

int main()
{
	test();
	return 0;
}

我们释放pst时使用free
【C++】内存管理_第3张图片
故此时需要使用delete,
由delete先调用析构函数,
再调用operator delete进行free掉pst指向的空间


案例二:

class A
{
public:
	A()
	{
		cout << "A()" << endl;
		_a = 10;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = new A[10];
	delete p;
 	//free(p);
	return 0;
}

VS场景下:
当我们释放p时没有使用delete[],而是使用delete与free
当使用delete时,只会调用一次析构函数,
【C++】内存管理_第4张图片
但这并不是报错的原因,当我们进行new10个自定义类型时,实际上会申请44字节大小的空间,在这里插入图片描述

当我们指定生成多少个对象时,编译器实际就知道调用构造函数的确切的个数,那么编译器从哪里知道要调用多少次析构函数呢,其中的关键就出在这个44字节中,
【C++】内存管理_第5张图片
最前的4个字节是用来存放要调用析构函数的个数,这样编译器就知道如何操作了,而其new A[10]返回的指针是p所指向的位置,这样我们free或者operator delete就会出错

总结:
要使用匹配的内存管理的操作符或函数。

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