内存分布,内存管理,new与delete(c/c++内存管理)

1. 引言

学习之前先做两道题,来复习一下变量的内存分布。

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = {1, 2, 3, 4};
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)*4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
free (ptr1);
free (ptr3);
}

在这里插入图片描述
在这里插入图片描述
在c语言中我们使用malloc,relloc,calloc,来动态开辟空间。
经常会问到这三者的区别。
那么c++为什么还要继续用new与delete?
问题先不回答,只有先了解了使用,才能谈到区别

2. c++中new与delete

2.1 用法区别

new和delete是操作符,传入对象个数,不需要强转
malloc和free是函数,传入字节数,需要强转

内置类型

    	//c语言
	int *p = (int*)malloc(sizeof(int));
	int *p1 = (int*)malloc(sizeof(int)* 10);
	free(p);
	free(p1);
	

	//c++
	int *p2 = new int;
	//定义并初始化
	int *p3 = new int(10);

	//定义数组
	//那么可以像上面也初始化呢,不行的
	//int *p4=new int[10](1);
	int *p4 = new int[10];//只能调用默认构造函数
	

	delete p2;
	delete p3;
	delete[] p4;

也就是说对于内置类型,malloc/free与new/delete区别不大,就是new一个元素的时候,申请单个空间,可以初始化,new[]申请多个连续空间,delete数组的时候需要delete加[ ]。对于内置类型一般不会申请失败。

2.2 调用构造函数与析构函数

内置类型区别不大,那么对于自定义类型呢?
malloc,d1对象和只能看见数组的第一个对象没有初始化,窗口没有任何输出
在这里插入图片描述
new的话,调用了自定义类型的默认构造函数初始化,输出1+10次
在这里插入图片描述
delete的话,调用了析构函数一共1+10次。
在这里插入图片描述

  1. malloc 开空间 free 释放空间
  2. new 开空间+构造函数初始化 delete 析构函数+释放空间
    这里对析构函数有点迷了,delete一定是先调用析构函数的,而且时刻记住析构函数是资源清理工作,构造函数是初始化。

3. opeator new 与opeator delet函数

opeator new 与opeator delet是c++申请空间的库函数。用法与malloc和free一样。不会调用构造函数。那它到底有什么意义。我们申请一个整形最大值的空间。(31位全为1,最高位为符号位0)
c语言处理错误的方式一般是返回错误码,申请失败的话,malloc返回0。
在这里插入图片描述

c++是面向对象语言,所以要有面向对象的处理模式,他错误一般是抛异常。
在这里插入图片描述
我们可以用try…catch语句捕获它。可以看到扩容失败
在这里插入图片描述
我们来读一下operator new的源码。

/*
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);

他底层实际上是是调用了malloc,只不过在失败的时候会抛出异常。

operator delete的源码如下:

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 delete也是调用了free。只不过free又调用了_free_dbg

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

所以实际上
new
operator new + 构造函数
operator new 相当于 malloc且malloc失败抛出异常
delete
析构函数+operator delete
一定是先析构,清理对象内部资源。然后在free对象(假如先free对象不就出问题了)
operator delete 相当于free

4. opeator new 与opeator delet的类专属重载

struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List l;
return 0;
}

假如这个链表,需要频繁的使用创建节点,释放节点。频繁的去找系统申请,释放,效率低还有内存碎片等问题。就像家里给生活费,用一次给一次很不方便,一次给一个月的生活费就不用在麻烦家里。这里的场景也是一样的,系统一次性给你一块内存,你自己去折腾。也就是我们通常所叫的池化技术,内存池就是其一种机制。

在回到我们这个问题上,正常情况下,使用new,new调用operator new+构造函数,全局的operator new就会调用malloc,那么就会向系统申请,我们将operator new 重载为成员函数,这样new就会调用我们自己实现的operator new +构造函数

5. 定位new表达式

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;

};


int main()
{
	
	Date* p1 = new Date(2000, 1, 1);
	//Date* p2 = new Date[10];
	return 0;
}

new一个日期类对象,并初始化空间。因为没有默认构造函数,所以对于申请10个对象的数组我们没有办法。

想要让一次性申请多个对象成功,第一个办法把构造函数变成默认构造函数。假如不允许呢?
继续想办法
首先我们知道new是operator new和构造函数。那么我们这样先调用operator new,在调用构造函数。

Date* p2 = (Date*)operator new (sizeof(Date) * 10);
	//构造函数该怎么调用

我们发现构造函数是我们定义对象的时候自动调用的,怎么显示调用它呢?

现在我们想要显示的调用构造函数,来初始化一块已有的空间。
在这里插入图片描述
这样就完成了显示调用。当然还要清理资源,释放空间。
在这里插入图片描述

Date* p2 = (Date*)operator new (sizeof(Date) * 10);
	//构造函数该怎么调用
	int n = 10;
	for (int i = 0; i < 10; i++)
	{
       new(p2+i)Date(2000, 1, 2);
	}
	//析构函数可以直接显示调用
	for (int i = 0; i < 10; i ++)
	{
		(p2 + i)->~Date();
	}
	operator delete(p2);

5.1 定位new总结:

定位new,语法就是,new(指针)类型(参数列表)。
当我们没有默认构造函数而且我们不能把他的构造函数改成默认构造函数,我们申请多个空间//Date* p2 = new Date[10];不能用,因为它调用默认构造函数。但我们知道new是调用operator new+构造函数。所以我们先调用operator new,由于构造函数对象生成默认调用,然后在显示的调用构造函数去初始化它。

6. new与delete实现原理总结

对于内置类型

对于malloc和free区别不大。new在申请空间失败时会抛异常,malloc会返回NULL。不过对于内置类型基本不会申请失败。

对于自定义类型

new:

  1. 调用operator new(它在调用malloc,malloc失败会抛异常)申请空间。
  2. 调用构造函数初始化空间。

delete:

  1. 调用析构函数清理对象中的资源。
  2. 调用operator delete(free)释放空间。

new T[N]的原理

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

delete[]的原理

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

在区分一下概念
A a 是在栈上分配的内存,生命周期为当前作用域。定义时调用构造函数。

A *a是定义了一个指向A的对象的指针,在这里它还没有指向任何对象。

A *aaa = new A;
这是在堆上分配一块内存用来存放一个A的对象,自动调用它的构造函数。然后把这块内存的首地址赋给A的指针aaa,除非运行代码delete aaa,这个对象一直存在,即使指针aaa生命周期结束(这就是传说的内存泻露);

附:如何在堆上申请4g内存

开始测试:

//1G=1024m=1024*1024kb=1024*1024*1024byte
void* p1 = operator new( );

2g无法申请(整型溢出,记得在1024后面+u代表无符号整形)
在这里插入图片描述
1.5g可以

在这里插入图片描述
经过测试1.8g几乎是极限。

其实很容易想到,进程地址空间32位的话是232字节,就是4g,其中1g为内核空间,剩余3g是用户。堆其实很大,大概2g。
在这里插入图片描述

我们将进程地址空间变为64位,264,就是264字节=17179869184G=16777216T
然后进行申请就完成了2g的申请。
在这里插入图片描述
4g也同样可以
在这里插入图片描述

主要考查的是操作系统中对进程地址空间和虚拟内存的理解。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楠c

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

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

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

打赏作者

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

抵扣说明:

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

余额充值