C++内存管理

目录

一、C\C++内存分布

二、C++内存管理方式

1.new/delete操作内置类型

2.new/delete操作自定义类型

三、new申请失败的处理

四、new和delete的底层

operator new与operator delete函数

五、定位new表达式(placement-new) 

         概念:

使用格式:

使用场景:

六、new/delete和malloc/free的区别


一、C\C++内存分布

我们在写代码的时候,需要不同的变量,有的时候需要局部变量,有的时候需要全局变量,还有的时候需要静态变量动态申请的变量,因为我们有各种各样的需求,所以内存被划分为如下几个区域:C++内存管理_第1张图片

它们分别在栈区、堆区、静态区存储。并且栈是向下生长,堆是向上生长。

二、C++内存管理方式

C语言的内存管理方式C++也可以使用,但是在有些地方使用C的内存管理方式会很麻烦,所以祖师爷搞出了一套新的玩法,通过newdelete对内存进行管理。

1.new/delete操作内置类型

int main()
{
	int* ptr1 = new int;//动态申请1个int类型的空间
	delete ptr1;        //释放空间
	int* ptr2 = new int(10);//动态申请1个int类型的空间并且初始化为10
	delete ptr2;
	int* ptr3 = new int[10];//动态申请10个int类型的空间
	delete[] ptr3;
	int* ptr4 = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };//动态申请10个
                                                    //int类型的空间并初始化
	delete[] ptr4;      
	int* ptr5 = new int[10]{ 1,2,3,4,5 };//可以部分初始化
                                         //未初始化的部分会自动初始化为0
	delete[] ptr5;
	return 0;
}

我们要动态申请只需要new后面根数据类型就可以了,不需要强转也不需要检查,并且可以用圆括号()初始化。如果我们需要连续申请多个空间在数据类型后面加上方括号[ ]就可以,初始化需要在后面跟上花括号{ },和C语言的数组用法一样,如果部分初始化,后续数据会自动初始化为0。

释放使用delete就可以,连续释放多个空间需要在delete后面跟方括号[ ]

对于内置类型其实new/delete和malloc/free区别其实不大,只不过是new/delete写起来更加舒服,对于自定义类型区别就很大了。

建议:不要和malloc和free混着用,配对使用。

2.new/delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;

	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
	//还会调用构造和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;
	// 内置类型几乎是一样的
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;
	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;
	return 0;
}

new/delete对于自定义类型,不仅会开空间,并且会调用默认构造和析构函数进行初始化,而malloc/free不会。

如果没有默认构造或者我们有需求,我们可以手动传参。下面这两种方式都可以,但要注意自定义类型手动初始化不可以省略。

	A* p6 = new A[4]{ 0,1,1,1 };//这个地方不可以省略,不给的话怎么知道初始化成什么呢?
	A* p7 = new A[4]{ A(1),A(2),A(3),A(4)};//编译器会进行优化直接构造

三、new申请失败的处理

在C语言中malloc失败了一般是返回值,而面向对象的语言更喜欢抛异常。异常是需要捕获的,所以我们需要用try和catch。具体使用方法如下:

C++内存管理_第2张图片

C++内存管理_第3张图片

 如果申请失败,会自动跳转到catch,然后把异常抛出去。

四、new和delete的底层

如果我们是祖师爷,我们要搞new和delete我们会怎么做呢?我们肯定会去用C语言提供好malloc和free呀,但是它们能不能符合我们的要求呢?显然不能,因为我们希望申请空间失败的时候抛异常而不是返回值,因此C++库里面提供了两个全局函数。

operator new与operator delete函数

new和delete是C++为我们提供动态内存申请的操作符,而operator new 和operator delete是C++的两个全局函数,new在底层调用operator new来申请空间,并且失败由它来抛异常。delete在底层调用operator delete来释放空间。那它们两个又是怎么通过什么实现的呢?

下面是operator new和operator delete的源代码

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

通过上述两个全局函数的实现可以知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行申请失败就抛异常。operator delete 最终是通过free来释放空间的。
也就是说它们的调用关系是这样的:

C++内存管理_第4张图片

 问题:为什么delete要先调用析构在释放空间呢?可不可以释放空间在析构呢?

答案是不可以。我们看下面一段代码:

int main()
{
	Stack* p1 = new Stack;
	delete p1;
	return 0;
}

在上面的代码中,我们new了一个栈对象,它的内存布局是这样的:

C++内存管理_第5张图片

 p1是在栈上的,然后我们在堆上new了一块空间,p1指向堆上的空间,因为stack内部构造函数也会申请空间,所以在堆上又申请了一块空间,arr指向那块空间,析构函数先释放arr指向的那块空间,然后再把p1指向的空间释放了。如果我们先释放p1指向的那块空间,那还能找到arr指向的那块空间吗?就会发生内存泄漏。

五、定位new表达式(placement-new) 

概念:


定位new是对一个已经分配的空间调用构造函数初始化对象。

使用格式:

new(place_address)type  或者new(place_address)type(parameter)

place_address:必须是一个指针

type:类型

parameter:默认构造如果有参数,需要传参


使用场景:


定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

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();//调用显示析构,在这个地方如果不写的话,是不会自动调用析构的。
             //因为p1是个指针,内置类型,因此不会自动调用。
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}


 

六、new/delete和malloc/free的区别

1.new和delete是操作符,malloc和ree是库函数

2.malloc申请的空间不会初始化,而new会初始化

3.malloc需要强转,new不需要强转,只需要跟上空间类型

4.new不需要判空,申请失败,会抛异常,malloc需要判空,申请失败会返回值

5.new/delete会对自定义类型调用构造函数和析构函数完成初始化和销毁,malloc/free则不会

6.new申请空间时只需要跟上类型就可以,不需要手动输入大小,malloc需要手动计算大小并输入

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