C堆,栈,RAII
堆英文名称heap,在内存管理的语境下,指的是动态分配的内存空间,这个和数据结构的堆是两回事。
这里的内存,被分配之后需要手动释放,否则会引发内存泄漏。
那怎么申请一个堆内存空间呢?
C语言中使用voidmalloc(sizetsize)来申请一块内存空间,size为申请的字节数。使用voidfree(voidptr)来手动释放内存。
C则使用new和delete来申请释放内存。
C标准里有一个相关概念是自由存储区,英文是freestore,特指使用new和delete来分配和释放内存的区域。一般而言,这是堆的一个子集。
为什么有了malloc,free,C中还出现了new,delete呢?
实际上new,delete的底层实现是malloc,free;malloc只是单纯地申请一块内存空间,但是new不一样,C中包含面向对象的设计,当我们在new一个对象时,C不仅要向系统申请一块内存,还需要构造这个对象,调用构造函数,而delete时,则需要调用类的析构函数,然后归还内存空间。
实际上new的操作类似于这样:Tp;voidmemoperatornew(sizeof(T));分配内存,其内部调用malloctry{pstaticcastT(mem);类型转换pT::T(。。。);调用构造函数returnp;}catch(。。。){operatordelete(p);throw;}
如果申请内存成功,并且调用构造函数正常,则对象构造成功,否则释放申请的内存,抛出badalloc异常。
而delete的操作类似于这样:pT::T();调用析构函数operatordelete(p);释放内存,内部调用free
先调用析构函数,再释放内存,如果反过来,先释放内存就没办法调用析构函数了嘛?是不是?
栈英文名称stack,在内存管理的语境下,指的是函数调用过程中产生的本地变量和调用数据的区域。这个栈和数据结构里的栈高度相似,都满足后进先出(lastinfirstout或LIFO)。
我们先来看一段示例代码,来说明C里函数调用、本地变量是如何使用栈的。当然,这一过程取决于计算机的实际架构,具体细节可能有所不同,但原理上都是相通的,都会使用一个后进先出的结构。voidfoo(intn){}voidbar(intn){intan1;foo(a);}intmain(){bar(42);}
生长方向:栈是朝着地址减小的方向生长的,而堆是朝着地址增大的方向生长的。
当函数调用另外一个函数时,会把参数也压入栈里然后把下一行汇编指令的地址压入栈,并跳转到新的函数。新的函数进入后,首先做一些必须的保存工作,然后会调整栈指针,分配出本地变量所需的空间,随后执行函数中的代码,并在执行完毕之后,根据调用者压入栈的地址,返回到调用者未执行的代码中继续执行。
本地变量就保存在栈上,当函数执行完成之后,保存本地变量的栈内存就被释放掉了。
上述例子中本地变量是内置的类型,本地变量不光可以是内置的类型,还可以是复杂的类型,比如说类的对象,这时,如果函数调用结束之后或者发生异常时,编译器会自动调用类的析构函数,这个过程叫做栈展开(stackunwinding)。
例如:classA{public:A(){coutAendl;}A(){coutAendl;}};intmain(){try{Aa;throwerror;}catch(constchars){coutsendl;}return0;}
由于函数调用栈的是先进后出的执行过程,在某一个栈空间被弹出时,在它上面后进的空间一定已经被弹出了,不可能出现内存碎片。
另外,图中每种颜色都表示某个函数占用的栈空间。这部分空间有个特定的术语,叫做栈帧(stackframe)。
RAII
上面讲了堆和栈,堆只要正确的使用new和delete也不会造成内存溢出,但是这是比较难的,各种情况都会有意无意的导致内存泄漏。比如:1ApnewA;。。。这一大段代码抛异常了,delete没有被执行deletep;2,分配和释放不在同一个函数内AcreateA(){ApnewA;returnp;}voidf(){ApcreateA();。。。中间代码一长就很可能漏掉deletep;}3。返回子对象的坑classA{public:A(){}A(){}private:intma;};classB:publicA{private:intmb;};Acreate(){Bb;returnb;}
create函数返回父类A的对象,实际上函数内返回的是子类B,编译器不会报错,但是多半是不对的,这种现象称为对象切片,把子类包含的数据成员给切掉了。正确的我们应该返回pointer或pointerlikeclass。
下面引出我们的主角RAII,RAII(ResourceAcquisitionIsInitialization),字面意思,资源获取即初始化。
RAII其实是为了解决上述忘记delete引发的内存泄漏问题而出现的,我们只需要把要返回的内容封装成类的对象成员,而这个类的对象就是一个本地变量,而本地变量是在栈上的,当对象离开它的作用域后将自动调用析构函数,我们只需要在析构函数内释放堆内存空间就可以了。classA{public:A(inta1):ma(a){}A(){}intma;};classWrapper{public:Wrapper(ApaNULL):ptr(pa){}Wrapper(){deleteptr;coutWrapperendl;}Aget()const{returnptr;}private:Aptr;};Wrappercreate(){returnWrapper(newA);}intmain(){Wrapperwptrcreate();coutwptr。get()maendl;return0;}输出1Wrapper
这里只是做一个简单的实例,其实可以把它写成pointerlikeclass,让wptr更像一个指针。
总的来说,资源在构造函数内获得,在析构函数内释放,实例对象是在栈上的本地变量。
RAII通常可以用来:关闭文件释放互斥锁释放其他的系统资源。
好了,堆,栈,RAII先说到这里了,如果文章有错误的地方还请给我指出来,大家一起进步嘛。
如果觉得对你有帮助的话请程序员杨小哥点个赞,谢谢!
旧旗舰不支持PCIe4。0?来块PCIe3。0x8固态试试本文仅基于作者帮助朋友,对其未支持PCIe4。0的旗舰级处理器与主板原系统升级,使用WDBLACKAN1500达到媲美甚至超过市面上现有PCIe4。0固态实际使用效果的相关测试……
vivoX50Pro来袭!影像实力再提升,满满的都是看点说起当前影像能力最出众的机型,相信大部分都会想起前不久发布的vivoX50Pro。确实,这款手机的影像能力十分强大,不仅带来了具有革命性意义的全新微云台,将手机防抖体验带入了一……
一个崔永元为1天带来1亿的下载量,微博要凉了大家都知道这几天小崔由于在微博受气,转战今日头条的事情,而且在转向今日头条一天的新增粉丝就突破了500万。看来明星效应的效果确实非同凡响,即使再会套路营销的高人,一个新号要想做……
蔚来财报下的反思大众市场,还得要7月,对蔚来汽车而言,并不算友好。掉队二字,悄然与之发生联系。在造车三兄弟每月固定的销量PK榜上,蔚来成为了最后一名。当理想和小鹏纷纷突破8000辆大关的同时,蔚来逆势下……
壹号游戏掌机闪耀亮相2021BiliBiliWorld,惊喜Hello,亲爱的壹哥壹姐们,2021BiliBiliWorld上海站即将开启啦!BiliBiliWorld作为一个集演出、展览和互动游戏于一身的全方位、综合性娱乐嘉年华,已然……
这是只HiFi手表飞傲M5迷你播放器体验如果将MP3音乐播放器当作音乐爱好者的时代,那么无损音乐播放器时代则是音乐发烧爱好者的时代,但是市面上的发烧产品数量之多且质量也鱼目混杂,让许多发烧友要想选择出一款性价比较高的……
舒服,一锅端的体验真好听书软件有很多,但收费的也不少,听个书交会员这换谁都不乐意,今天威仔就给大家推荐一个免费且齐全的听书站点!海盗听书(全平台)https:www。tingxiaoshuo。……
美妆也在AI技术的赋能下,进入了产业智能化的通道近些年,国内AI、AR等先进技术不断发展成熟,家居、医疗等纷纷吹响了产业智能化的革命号角,美妆也在AI、AR技术的赋能下,进入了产业智能化的通道,从新一代年轻消费者成为助推AR……
可油可电,超级混动唐dmi周末景区自驾游周末跟家人一起去景区自驾游,太不容易等到周末应该去山里避避暑!这个天气一路上并不是那么凉快呀,空调一直打开,座椅通风也必须开着,不得不赞赏一下这个座椅通风的功能。智能语音说一句……
都说华为手机不要盲目选,其实看准配置就行华为MATE40系列华为的第一款华为MATE40系列毕竟是华为去年最新的旗舰产品,无论是性能还是设计,都相当出色,预算不足的朋友可以考虑华为Mate40,从4999开始,……
北京X7综合性能表现出色,备受消费者青睐对于普通老百姓来说,买车实惠和实用最重要,在价格合适的情况下,再去考虑配置、动力、品牌等因素,当然如果能够花小钱办大事,那自然是再好不过了。在10万元左右的价位,很多消费者都会……
专业的技术经验为赛题设置提供了绝佳的创作源泉航天龙梦出任软件杯设计大赛出题方及决赛评委侧记9月5日,历经多日精彩紧张的比赛角逐,第十届中国软件杯大学生软件设计大赛在北京圆满结束。航天科工二院七六所航天龙梦公司凭借专……