• 内存分配C/C++堆、栈及静态数据区详解


    本文纯属个人见解,是对面前学习的总结,如有描述不正确的地方还请高手指正~

        五大内存分区

        C++中,内存成分5个区,他们分别是堆、栈、自在存储区、全局/态静存储区和常量存储区。

        栈,就是那些由编译器在须要的时候分配,在不须要的时候主动清晰的变量的存储区。面里的变量通常是局部变量、函数数参等。

        堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的用应序程去制控,一般一个new就要对应一个delete。如果序程员没有释放掉,那么在序程束结后,操纵系统会主动回收。

        自在存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来束结自己的性命的。

        全局/态静存储区,全局变量和态静变量被分配到统一块内存中,在之前的C语言中,全局变量又分为初始化的和未初始化的,在C++面里没有这个分区了,他们独特占用统一块内存区。

        常量存储区,这是一块比拟殊特的存储区,他们面里放存的是常量,不答应改修(当然,你要通过非合法段手也可以改修,而且方法很多)

        确明分区堆与栈

        bbs上,堆与栈的分区问题,似乎是一个恒永的话题,由此可见,初学者对此往往是淆混不清的,所以我定决拿他第一个开刀。

        首先,我们举一个例子:

        void f() { int* p=new int[5]; }

        这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中放存了一个指向一块堆内存的指针p。在序程会先确定在堆中分配内存的巨细,然后调用operator new分配内存,然后返回这块内存的首址地,放入栈中,他在VC6下的汇编代码如下:

        00401028 push 14h

        0040102A call operator new (00401060)

        0040102F add esp,4

        00401032 mov dword ptr [ebp-8],eax

        00401035 mov eax,dword ptr [ebp-8]

        00401038 mov dword ptr [ebp-4],eax

        这里,我们为了单简并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了诉告编译器:我除删的是一个组数,VC6就会根据应相的Cookie信息去行进释放内存的作工。

        好了,我们回到我们的主题:堆和栈究竟有什么区别?

        要主的区别由以下几点:

        1、管理方法不同;

        2、空间巨细不同;

        3、是否生产碎片不同;

        4、成长方向不同;

        5、分配方法不同;

        6、分配效率不同;

        管理方法:对于栈来讲,是由编译器主动管理,无需我们手工制控;对于堆来讲,释放作工由序程员制控,易容生产memory leak

        空间巨细:一般来讲在32位系统下,堆内存可以到达4G的空间,从这个度角来看堆内存几乎是没有什么制约的。但是对于栈来讲,一般都是有必定的空间巨细的,例如,在VC6面下,默许的栈空间巨细是1M(好像是,记不清晰了)。当然,我们可以改修:

        打开工程,次依操纵菜单如下:Project->Setting->Link,在Category 中中选Output,然后在Reserve中设定堆栈的最大值和commit

        注意:reserve最小值为4Bytecommit是保留在虚拟内存的页件文面里,它设置的较大会使栈辟开较大的值,可能加增内存的开销和启动间时。

        碎片问题:对于堆来讲,繁频的new/delete势必会形成内存空间的不续连,从而形成量大的碎片,使序程效率下降。对于栈来讲,则不会存在这个问题,因为栈是先进后出的列队,他们是如此的一一对应,以至于永久都可不能有一个内存块从栈间中弹出,在他弹出之前,在他面下的进后的栈内容经已被弹出,详细的可以考参数据结构,这里我们就不再一一探讨了。

        成长方向:对于堆来讲,成长方向是向上的,也就是向着内存址地加增的方向;对于栈来讲,它的成长方向是向下的,是向着内存址地减小的方向长增。

        分配方法:堆都是动态分配的,没有态静分配的堆。栈有2种分配方法:态静分配和动态分配。态静分配是编译器实现的,比如局部变量的分配。动态分配由alloca函数行进分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器行进释放,无需我们手工现实。

        分配效率:栈是器机系统供给的数据结构,计算会机在底层对栈供给支撑:分配专门的寄存器放存栈的址地,压栈出栈都有专门的指令执行,这就定决了栈的效率比拟高。堆则是C/C++函数库供给的,它的机制是很杂复的,例如为了分配一块内存,库函数会按照必定的法算(详细的法算可以考参数据结构/操纵系统)在堆内存中索搜可用的够足巨细的空间,如果没有够足巨细的空间(是能可由于内存碎片太多),就有可能调用系统功能去加增序程数据段的内存空间,这样就有会机分到够足巨细的内存,然后行进返回。然显,堆的效率比栈要低很多。

        从这里我们可以看到,堆和栈比相,由于量大new/delete的用使,易容形成量大的内存碎片;由于没有专门的系统支撑,效率很低;由于可能发引用户态和核态心的换切,内存的请求,价值变得更加昂贵。所以栈在序程中是用应最普遍的,就算是函数的调用也利用栈去实现,函数调用过程当中的数参,返回址地,EBP和局部变量都采取栈的方法放存。所以,我们推荐大家尽量用栈,而不是用堆。

        每日一道理
    一个安静的夜晚,我独自一人,有些空虚,有些凄凉。坐在星空下,抬头仰望美丽天空,感觉真实却由虚幻,闪闪烁烁,似乎看来还有些跳动。美的一切总在瞬间,如同“海市蜃楼”般,也只是刹那间的一闪而过,当天空变得明亮,而这星星也早已一同退去……

        虽然栈有如此多众的利益,但是由于和堆比相不是那么活灵,有时候分配量大的内存空间,还是用堆好一些。

        无论是堆还是栈,都要止防越界象现的产生(除非你是故意使其越界),因为越界的结果要么是序程崩溃,要么是捣毁序程的堆、栈结构,生产以想不到的结果,就算是在你的序程行运过程当中,没有产生面下的问题,你还是要当心,说不定什么时候就崩掉,那时候debug可是相称难题的:)

        对了,还有一件事,如果有人把堆栈合起来讲,那它的意思是栈,可不是堆,呵呵,清晰了?

        static用来制控变量的存储方法和可见性

        函数外部定义的变量,在序程执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行束结时会释放掉,这样就生产了一个问题如果想将函数中此变量的值存保至下一次调用时,如何现实? 最易容想到的方法是定义一个全局的变量,但定义为一个全局变量有许多点缺,最显明的点缺是坏破了此变量的问访范围(使得在此函数中定义的变量,不仅仅受此函数制控)。

        须要一个数据象对为整个类而非某个象对服务,同时又力图不坏破类的装封性,即要求此成员隐藏在类的外部,对外可不见。

        static的外部机制:

        态静数据成员要在序程一开始行运时就必须存在。因为函数在序程行运中被调用,所以态静数据成员不能在任何函数内分配空间和初始化。

        这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头件文,那里有类声明;二是类定义的外部现实,那里有类的成员函数定义;三是用应序程的main()函数前的全局数据声明和定义处。

        态静数据成员要现实地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,不并行进现实的内存分配,所以在类声明中写成定义是错误的。它也不能在头件文中类声明的外部定义,因为那会形成在多个用使该类的源件文中,对其重复定义。

        static被引入以知告编译器,将变量存储在序程的态静存储区而非栈上空间,态静

        数据成员按定义现出的先后序顺次依初始化,注意态静成员套嵌时,要保障所套嵌的成员经已初始化了。清除时的序顺是初始化的反序顺。

        static的优势:

        可以节俭内存,因为它是全部象对所私有的,因此,对多个象对来讲,态静数据成员只存储一处,供全部象对共用。态静数据成员的值对每一个象对都是一样,但它的值是可以更新的。只要对态静数据成员的值更新一次,保障全部象对存取更新后的同相的值,这样可以进步间时效率。

        引用态静数据成员时,采取如下格式:

        <类名>::<态静成员名>

        如果态静数据成员的问访权限答应的话(public的成员),可在序程中,按上述格式

        来引用态静数据成员。

        PS:

        (1)类的态静成员函数是属于整个类而非类的象对,所以它没有this指针,这就致导

        了它仅能问访类的态静数据和态静成员函数。

        (2)不能将态静成员函数定义为虚函数。

        (3)由于态静成员声明于类中,操纵于其外,所以对其取址地操纵,就多少有些殊特

        ,变量址地是指向其数据类型的指针,函数址地类型是一个“nonmember函数指针”。

        (4)由于态静成员函数没有this指针,所以就差多不等同于nonmember函数,结果就

        生产了一个意想不到的利益:成为一个callback函数,使得我们得以将C++C-based X W

        indow系统结合,同时也胜利的用应于线程函数身上。

        (5)static并没有加增序程的时空开销,相反她还缩短了类子对类父态静成员的问访

        间时,节俭了类子的内存空间。

        (6)态静数据成员在<定义或说明>时面前加关键字static

        (7)态静数据成员是态静存储的,所以必须对它行进初始化。

        (8)态静成员初始化与一般数据成员初始化不同:

        初始化在类体外行进,而面前不加static,以免与一般态静变量或象对相淆混;

        初始化时不加该成员的问访权制约控符privatepublic等;

        初始化时用使作用域运算符来明标它所属类;

        所以我们得出态静数据成员初始化的格式:

        <数据类型><类名>::<态静数据成员名>=<>

        (9)为了止防类父的影响,可以在类子定义一个与类父同相的态静变量,以屏蔽类父的影响。这里有一点须要注意:我们说态静成员为类父和类子享共,但我们有重复定义了态静成员,这会不会发引错误呢?不会,我们的编译器采取了一种绝妙的法手:name-mangling 用以成生独一的标记。

        

        转载于:http://chinus.itpub.net/post/17198/105886

    文章结束给大家分享下程序员的一些笑话语录: 现在社会太数字化了,所以最好是有一个集很多功能于一身的设备!

  • 相关阅读:
    AWK
    正则表达式
    BASH
    C# 常用控件的一些属性及方法
    C# FTP
    C# Delegate
    DLL/EXE查看工具Dumpbin
    VBA 破解Excel工作表保护密码
    VB6 IP地址+网卡地址+网卡类型
    编程之路┊由C#风潮想起的——给初学编程者的忠告 ZT
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3069918.html
Copyright © 2020-2023  润新知