c语言动态分配内存,c++的动态内存分配
Yyds干货库存
在写之前,我们知道C支持C语言,也就是说C语言中的malloc等函数都可以在C中使用,但是C支持另外两个关键字,非常有用。我们需要看看C的动态内存。
C/C内存分配我记得,我刚认识C的时候,跟大家分享过程序虚拟地址空间的概念。无论是C的nalloc函数,还是我们现在要分享的new,都是在堆区打开空间,这是我们首先要记住的。
C语言内存管理模式C语言是通过函数动态内存开发的。标准库中有三个函数,这里就不赘述了。大家应该都知道吧。我就看看用法吧。
#包含stdio.h
#include assert.h
int main()
{
//malloc开放空间未初始化
int * P1=(int *)malloc(sizeof(int)* 4);
断言(P1);
//calloc开放空间初始化为0
int* p2=(int*)calloc(4,sizeof(int));
断言(p2);
//附加空间
p1=(int*)relloc(p1,sizeof(int)* 8);
免费(P1);
免费(p2);
返回0;
}
C内存管理模式C支持C语言,也就是说C可以使用这些函数。不过除了这些函数,C还增加了两个关键字,new和delete,分别针对malloc/calloc和free,C的模式比C的好。
为什么C增加了新的又删除了?众所周知,C语言的结构中是不支持函数的,于是大佬们提出了类的概念,类出现了,怕自己有时候会忘记初始化和清空内存,于是出现了构造函数和析构函数,让编译器自动调用。可以说,所有事物的出现都是为了让我们更好地使用语言,新增和删除也是如此。C语言开辟动态内存比较麻烦,对于自动类型来说很重要。
我们还发现了一个很直接的问题。我们每打开一个空间,都要强制进行类型转换,还需要判断内存是否打开。这个太麻烦了,不过这种事情在new不会发生。如果没有打开,编译器会抛出异常,所以我们不需要自己手动检查。
新对象。这样我先给你看内置类型和自定义类型,我准备和malloc对比一下。
#包括iostream
使用命名空间std
int main()
{
int * p1=new int
* p1=10
cout * p1 endl
返回0;
}
我们知道,在C中,内置的类行也是作为一个类使用的,我们可以在它是新的时候初始化它。
int main()
{
int * p=new int(0);
cout * p endl
返回0;
}
数组更简单,我们可以直接写。
int main()
{
int * p=new int[10];//新建10个int类行的空间
返回0;
}
我们也可以在新的空间中实例化,但是实例化应该被显示。
int main()
{
int* p=new int[10]{1,2,3 };
返回0;
}
您可能会发现,删除不会释放任何空间,这将导致内存泄漏。这里我们用另一个关键字,delete,更简单。
您可能对delete[]感到困惑。在这里,我们可以记住它。如果要清空数组的空间,最好用这个方法。也许对于内置的类行,您也可以使用delete,但是对于自定义的类行,您可能会报告一个错误。这里,我以后再说。
int main()
{
int * p1=new int
int* p2=new int[10]{1,2,3 };
删除P1;
删除[]p2;
返回0;
}
Malloc new我们需要对比一下Malloc和new的区别,这样才能知道C为什么这么喜欢new。
内置类型,先下结论吧。除了错误报告之外,这两个内置类行之间没有任何区别,它们都不会被该行初始化。这里就不说报错、异常、失败的信息和大家分享了。
int main()
{
int * P1=new int[10];
int * p2=(int *)malloc(sizeof(int)* 10);
断言(p2);
删除[]P1;
免费(p2);
返回0;
}
自定义类型对于自定义类型来说,两者有很大的区别。
先备课吧。
A级
{
公共:
A(int a=0,int b=0)
:_a(一)
,_b(b)
{
cout“constructor”endl;
}
~A()
{
Cout“析构函数”endl
}
私人:
int _ a;
int _ b;
};
Malloc直接打开空间,里面的构造函数不会被调用,析构函数在空闲的时候也不会被调用。
int main()
{
a* aa=(a*)malloc(sizeof(A));
免费(aa);
返回0;
}
而new和delete分别调用构造函数和析构函数来完成初始化。
int main()
{
A* aa=新A;
删除aa;
返回0;
}
操作符new和操作符delete函数在这里看起来像new和delete的重载。记住,这不是,或者名字有点怪。这是C中的一个全局函数,使用方法和malloc一样,功能一样。它不会调用构造函数和析构函数。
和新增删除是用户申请和释放动态内存的操作符,新增操作符和删除操作符是系统提供的全局功能。New调用底部的操作符new全局函数申请空间,delete通过底部的操作符delete全局函数释放空间。
int main()
{
A* aa=(A*)算子new(sizeof(A));
操作员删除(aa);
返回0;
}
根据原理源代码可以发现,其实运算符new和运算符delete函数本质上都是malloc和free的封装,就是错误信息有点不一样,封装的错误信息是异常的。
运算符new的原理是malloc
void *__CRTDECL运算符new(size _ t size)_ throw 1(_ STD bad _ alloc)
{
//尝试分配大小字节
void * p;
while ((p=malloc(size))==0)
if (_callnewh(size)==0)
{
//报告没有内存
//如果内存应用失败,这里会抛出bad_alloc类型异常。
static const STD:bad _ alloc nomem;
_RAISE(诺姆);
}
返回(p);
}
运算符删除原则
void运算符删除(void *pUserData)
{
_ CrtMemBlockHeader * pHead
RTCCALLBACK(_RTC_Free_hook,(pUserData,0));
if (pUserData==NULL)
返回;
_ mlock(_ HEAP _ LOCK);/*阻止其他线程*/
_ _尝试
/*获取指向内存块头的指针*/
pHead=pHdr(puser data);
/*验证块类型*/
_ ASSERTE(_ BLOCK _ TYPE _ IS _ VALID(pHead-nBlockUse));
_free_dbg(pUserData,pHead-nBlockUse);//注意,C语言中的free就是这个函数
_ _最后
_ munlock(_ HEAP _ LOCK);/*释放其他线程*/
_ _ END _ TRY _最终
返回;
}
为什么会出现这两种功能?这两个函数不是为我们调用的,而是为new的底层调用的。我们的new对象相当于call operator new和call object的构造函数,这也是它们出现的原因。
可以拆解一下看看。
删除[]我们可以这样理解。对于内置类型,不需要讨论,功能都差不多。但是对于自定义类型,有一个很大的问题。
在释放的对象空间上执行n次析构函数,完成n个对象中的资源清理,调用operator delete[]释放空间,在operator delete[]中实际调用operator delete释放空间。先看结果吧。
Delete[]析构相应的次数
int main()
{
A* aa=新A[3];
删除[]aa;
返回0;
}
删除一次析构函数,就会报错。
int main()
{
A* aa=新A[3];
删除aa;
返回0;
}
在这里,我想介绍一个内存池的概念。我们都知道malloc和new正在堆上创建空间。如果我们多次创造空间,效率会不会有点慢?想想吧。我们创建一次,打开几千次,每次都申请。我们在想,是否可以单独划分出一个区域,提供我们想要创造空间的物件。这就是内存池的最初想法。你可能会困惑。我们可以做这样的类比。堆积就像你在学校吃的每一吨饭,每一餐都要向你父亲要钱。然后内存池就像月初直接问你爸要这个月的生活费,一个月一次,肯定是后者效率更高。
那么我们如何使用内存池呢?标准库中也提供了一个。这里,我们需要在类中重写operator new和operator delete函数。先来了解一下用法。我们先不细说。后面可能会有高并发内存池项目给大家分享,不过这个时间有点长。
结构列表节点
{
ListNode * _ next
ListNode * _ prev
int _ data
//申请空间的是回内存池。
void*运算符new(size_t n)
{
void * p=nullptr
p=分配器列表节点()。分配(1);
cout 内存池分配 endl
返回p;
}
void运算符删除(void* p)
{
分配器列表节点()。解除分配((ListNode*)p,1);
cout 内存池解除分配 endl
}
};
根据考试成绩分等级排列的投考者的名单
{
公共:
列表()
{
_head=新列表节点;
_ head-_ next=_ head;
_ head-_ prev=_ head;
}
~列表()
{
ListNode * cur=_ head-_ next;
而(cur!=_head)
{
ListNode * next=cur-_ next;
删除cur
cur=next
}
delete _ head
_ head=nullptr
}
私人:
ListNode * _ head
};
int main()
{
列表L1;
返回0;
}
定位new我们已经知道使用运算符new创建的空间不会被初始化,现在也不能通过对象显式调用构造函数,也就是说如果把成员变量修改为,肯定会破包。不过,C在这里也提供了一种定位new的技术来帮助我们再次实例化。我们先来看看用法。
A级
{
公共:
A(int a=0)
:_a(一)
{
}
私人:
int _ a;
};
int main()
{
A* a=(A*)算子new(sizeof(A));
//定位新的
新(一)一(1);
返回0;
}
从这里我们可以知道,定位新有以下两种用法。
New(要初始化的指针)引用对应的类行,直接调用默认构造函数new(要初始化的指针)。指针引用对应的类行(构造函数要传递的参数),调用对应的构造函数。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。