new/delete(C++)

一.内存区域的划分

在这里插入图片描述
那问题就是:为什么要分这些区域?
答:方便管理
这里有个误区,可能认为const修饰的都在静态区,其实不一定,下面的例子就是变成了常变量,在栈上
在这里插入图片描述

二.C++内存管理方式

C语言中内存管理方式:malloc/realloc/calloc/free
C++内存管理方式:new/delete

1.new/delete内置类型操作

这是在堆上创建1个int大小的对象,不需要强转,如果没写个数,默认是1个

int main()
{
	int* p1 = new int;
}

这是在堆上创建10个int大小的对象

int main()
{
	int* p2 = new int[10];
}

释放空间如果是默认的就直接释放,如果是[]创建的要用[]释放

int main()
{
	int* p1 = new int;
	int* p2 = new int[10];
	delete p1;
	delete[] p2;
	return 0;
}

new也可以控制初始化

int main()
{
	int* p3 = new int(10);//new 1个int对象,初始化成10
	int* p4 = new int[10]{1,2,3,4,5};//new 10个对象,初始化成1,2,3,4,5后面全是0
	delete p3;
	delete[] p4;
	return 0;
}

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

new在创建自定义类型时,开空间+构造函数
举个创建链表例子:

#include <iostream>

using namespace std;

class ListNode
{
public:
	ListNode(int val = 0)
		:_next(nullptr)
		,_val(val)
	{}
	//创建不带哨兵位的
	ListNode* ListNodeCreate(int n)
	{
		ListNode head(-1);//哨兵位,但是是局部变量,出了域会自动销毁
		ListNode* tail = &head;
		int val = 0;
		printf("一次输入%d个节点的值:", n);
		for (int i = 0; i < n; i++)
		{
			cin >> val;
			tail->_next = new ListNode(val);
			tail = tail->_next;
		}
		return head._next;
	}
private:
	ListNode* _next;
	int _val;
};

int main()
{
	ListNode* list = ListNode().ListNodeCreate(5);
	return 0;
}

相比较C语言简洁很多,而且不用手动检查,new失败了以后会抛异常
这里演示一下:

void Func()
{
	int n = 0;
	while (1)
	{
		int* p = new int[1024 * 1024 * 100];//这里是1024字节 = 1KB 
		cout << n << "->" << p << endl;     //1024KB = 1MB
		n++;                                //也就是每次new创建400MB
	}
}

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

在这里插入图片描述
malloc不会抛异常,得自己手动检查,如果创建不出来会以空指针的方式循环:

void Func()
{
	int n = 0;
	while (1)
	{
		int* p = (int*)malloc(sizeof(int) * (1024 * 1024 * 100));
		cout << n << "->" << p << endl;   
		n++;    
	}
}

在这里插入图片描述
当然如果想把new抛异常给捕获可以用以下代码:

int main()
{
	try
	{
		Func();
	}
	catch (const exception& e)//如果出了异常,直接捕获
	{
		cout << e.what() << endl;
	}
	return 0;
}

在这里插入图片描述

三.operator new 与operator delete函数

注意:C++中的new是操作符,C语言malloc是函数
  operator new 和operator delete 是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,deletor在底层通过operator delete全局函数来释放空间
  用大白话来讲就是operator new是对malloc的封装,operator delete就是对free的封装
下面是底层原理,有兴趣可以看看


//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

看以下反汇编:

#include <iostream>

using namespace std;

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

int main()
{
	A* ptr = new A;
	return 0;
}

在这里插入图片描述
当然除了operator new 还有operator new[ ]
在这里插入图片描述
operator new[ ]的底层是operator new
在这里插入图片描述
感觉有点乱,画个图:
在这里插入图片描述
举个类似的例子:
sizeof 运算符 编译时根据类型大小定义,自定义类型根据内存对齐规则计算对象大小
strlen 函数 运行时计算大小
可以用operator new创建堆

int main()
{
	int* p1 = (int*)operator new(10 * 4);
	return 0;
}

delete也是同样的道理,但是和new有一点不一样
下面代码先调析构函数还是先delete

#include <iostream>

using namespace std;

class Stack
{
public:
	Stack()
	{
		_a = (int*)malloc(sizeof(int)*10);
		_size = 0;
	}
	~Stack()
	{
		free(_a);
		_size = 0;
	}
private:
	int* _a;
	int _size;
};

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

这里先说结论:先调析构函数,后调delete
原因如图:如果先delete,_a就找不到堆了
在这里插入图片描述
这里还有一个小点:

#include <iostream>

using namespace std;

class A
{
public:
	A()
		:_a(0)
	{}
	~A()
	{}
private:
	int _a;
};

int main()
{
	A* ptr1 = new A;
	A* ptr2 = new A[10];
	delete ptr1;
	delete[] ptr2;
	return 0;
}

通过以上代码的反汇编可以发现:明明是创建了40个字节,却开出44个字节
在这里插入图片描述
这个多出来的4个字节存的是数字10:
在这里插入图片描述
原因如下:operator new[]中面至少有个10,但operator delete[]啥都没有
在这里插入图片描述
也不是所有的new [ ]都会多出来4个字节记录
内置类型delete又不会调用析构函数,所以不用
在这里插入图片描述
如果把上述代码的析构函数删了

#include <iostream>

using namespace std;

class A
{
public:
	A()
		:_a(0)
	{}
private:
	int _a;
};

int main()
{
	A* ptr1 = new A;
	A* ptr2 = new A[10];
	delete ptr1;
	delete[] ptr2;
	return 0;
}

发现是40,是因为系统发现默认析构函数啥事没干,就不多创建4个字节
在这里插入图片描述

如果new的成员函数和delete不匹配会怎样:

如下述代码:

#include <iostream>

using namespace std;

class Date
{
public:
	Date()
	{
		cout << "Date()" << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
};
int main()
{
	Date* p1 = new Date[10];
	delete p1;
	return 0;
}

会发现报错了,并且构造函数调用了10次,但析构函数调用1次就报错了
在这里插入图片描述
这是因为在堆上的空间不能分段释放,这里画个图:
在这里插入图片描述
从内存中可以看见,p1的指针前面4个字节存储的是10
在这里插入图片描述

四.定位new表达式(了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
我认为就是把自定义类型new的过程显现出来
代码如下:仅作了解即可,如果一块空间需要显示调用构造函数,才用

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

int main()
{
	A* p2 = (A*)operator new(sizeof(A));
	// 内存池
	// 显示调用构造函数对一块已经有的空间初始化
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

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

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[ ]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

六.注意的几点:

在C++中,new和malloc需要头文件malloc.h,只是平时这个头文件已经被其他头文件所包含了,用的时候很少单独引入

练习一:

ClassA *pclassa = new ClassA[5];

delete pclassa;

c++语言中,类ClassA的构造函数和析构函数的执行次数分别为( )
答案是可能崩溃,原因在第三点结尾讲了

练习二:

使用 char* p = new char[100]申请一段内存,然后使用delete p释放,有什么问题?( )
答案是没问题,但最好不要用,只有调析构函数的时候才可能遇到练习一报错的问题

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅碎时光807

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值