收藏 分享(赏)

C++中堆和栈的区别.doc

上传人:dwy79026 文档编号:7596347 上传时间:2019-05-21 格式:DOC 页数:7 大小:44KB
下载 相关 举报
C++中堆和栈的区别.doc_第1页
第1页 / 共7页
C++中堆和栈的区别.doc_第2页
第2页 / 共7页
C++中堆和栈的区别.doc_第3页
第3页 / 共7页
C++中堆和栈的区别.doc_第4页
第4页 / 共7页
C++中堆和栈的区别.doc_第5页
第5页 / 共7页
点击查看更多>>
资源描述

1、C+中堆和栈的区别,自由存储区、全局/静态存储区和常量存储区 文章来自一个论坛里的回帖,哪个论坛记不得了!在 C+中,内存分成 5 个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。堆,就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。自由存储区,就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free

2、来结束自己的生命的。全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void*来访问和操纵,程序结束后由系统自行释放),在 C+里面没有这个区分了,他们共同占用同一块内存区。常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)明确区分堆与栈在 bbs 上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决

3、定拿他第一个开刀。首先,我们举一个例子:void f() int* p=new int5; 这条短短的一句话就包含了堆与栈,看到 new,我们首先就应该想到,我们分配了一块堆内存,那么指针 p 呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针 p。在程序会先确定在堆中分配内存的大小,然后调用 operator new 分配内存,然后返回这块内存的首地址,放入栈中,他在 VC6 下的汇编代码如下:00401028 push 14h0040102A call operator new (00401060)0040102F add esp,400401032

4、mov dword ptr ebp-8,eax00401035 mov eax,dword ptr ebp-800401038 mov dword ptr ebp-4,eax这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是 delete p么?澳,错了,应该是 delete p,这是为了告诉编译器:我删除的是一个数组,VC6 就会根据相应的 Cookie 信息去进行释放内存的工作。好了,我们回到我们的主题:堆和栈究竟有什么区别? 主要的区别由以下几点:1、管理方式不同;2、空间大小不同;3、能否产生碎片不同;4、生长方向不同;5、分配方式不同;6、分配效率不同;管理方式:对于栈来讲,是

5、由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生 memory leak。空间大小:一般来讲在 32 位系统下,堆内存可以达到 4G 的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在 VC6 下面,默认的栈空间大小是 1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project-Setting-Link,在 Category 中选中 Output,然后在 Reserve 中设定堆栈的最大值和 commit。注意:reserve 最小值为 4Byte;commit 是保留在虚拟内存

6、的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。碎片问题:对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。分配方式:堆都是动态分配的,没有静态分配的堆。栈有 2

7、 种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 alloca 函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C+函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数

8、据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。从这里我们可以看到,堆和栈相比,由于大量 new/delete 的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP 和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。无论是堆还是栈,都要防止越界现象的发生(除非你是

9、故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候 debug 可是相当困难的:)对了,还有一件事,如果有人把堆栈合起来说,那它的意思是栈,可不是堆,呵呵,清楚了?static 用来控制变量的存储方式和可见性函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量

10、有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。static 的内部机制:静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main()函数前的全局数据声明和定义处。静态数据成员要实际地分配空间,故不能在类的声明中定义(只能

11、声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。static 的优势:可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成

12、员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。引用静态数据成员时,采用如下格式::如果静态数据成员的访问权限允许的话(即 public 的成员),可在程序中,按上述格式来引用静态数据成员。ps:(1)类的静态成员函数是属于整个类而非类的对象,所以它没有 this 指针,这就导致了它仅能访问类的静态数据和静态成员函数。(2)不能将静态成员函数定义为虚函数。(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember 函数指针”。(4)由于静态成员函数没有 this 指针,所以就差不

13、多等同于 nonmember 函数,结果就产生了一个意想不到的好处:成为一个 callback 函数,使得我们得以将 c+和 c-based x window 系统结合,同时也成功的应用于线程函数身上。(5)static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。(6)静态数据成员在时前面加关键字 static。(7)静态数据成员是静态存储的,所以必须对它进行初始化。(8)静态成员初始化与一般数据成员初始化不同:初始化在类体外进行,而前面不加 static,以免与一般静态变量或对象相混淆;初始化时不加该成员的访问权限控制符 private,pu

14、blic 等;初始化时使用作用域运算符来标明它所属类;所以我们得出静态数据成员初始化的格式::=(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有 重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。【转】全局变量静态变量static 声明的变量在 C 语言中有两方面的特征:1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。2)、变量用 static 告知

15、编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。Tips:A.若全局变量仅在单个 C 文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static 变量(这样的函数被称为:带“内部存储器”功能的的函数)E.函数中必须要使用 static 变量情况:比如当某函数的返回值为指针类型时,则必须是 static 的局部

16、变量的地址作为返回值,若为 auto 类型,则返回为错指针。函数前加 static 使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。扩展分析:术语 static 有着不寻常的历史.起初,在 C 中引入关键字 static 是为了表示退出一个块后仍然存在的局部变量。随后,static 在 C 中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用 static 关键字来表示这第二种

17、含义。最后,C+重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数(与 Java 中此关键字的含义相同)。全局变量、静态全局变量、静态局部变量和局部变量的区别变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。全局

18、变量(外部变量)的说明之前再冠以 static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存

19、期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件static 全局变量与普通的全局变量有什么区别:static 全局变量只初使化一次,防止在其他文件单元中被引用;static 局部变量和普通局部变量有什么区别:static 局部变量只被初始化一次,下一次依据上一次结果值;static 函数与普通函数有什么区别:static 函

20、数在内存中只有一份,普通函数在每个被调用中维持一份拷贝全局变量和静态变量如果没有手工初始化,则由编译器初始化为 0。局部变量的值不可知。堆和栈 内存分配中堆栈和堆有什么区别呀?对了! 这就是说动态分配内存时我们采用了 Heap, 静态时就可以使用 stack。 VCL 的对象因此必须使用 new 来分配内存, TForm form;这样的写法在 VCL 中绝对不允许。 下面 Copy paste 一下 所有的 VCL 对象都通过指针进行引用。C+ Builder 中不存在任何 VCL 类的任何静态或局部实例,从表面上看,这主要与 VCL 是从 Object Pascal 演变而来有关,而真正的

21、深层次原因是由面向对象技术的关联与委托模型决定的。 基于性能和内存高效分配的原因,在堆(heap)中分配对象比在栈(stack) 中分配对象的效率要高。因此,Delphi 最大限度地简化了语言的语法,强制用户必须在堆中分配对象。因为 Pascal 的高度机构性,程序员所面临的指针问题比 C+要简单得多。VCL 对象的构造函数自动在堆中分配对象而不需要指针的参与。对于程序员来说,没有复杂指针的 Delphi 确实好用,但程序员必须记住,没有指针事实上意味着指针无处不在Delphi 根本就不允许程序员创建任何一个非指针的对象!注意,所有的其他数据类型,诸如字符串、整数、数组和结构(记录)等,都可以

22、生命为静态的或动态的,此项限制仅适用于对象。 例如以下代码: var T: TObject; begin T := New(TObject, Create); T.Free; end; 上述代码在 Delphi 中是不可能编译通过的。但它事实上就是 Delphi 处理对象的方式。Delphi 对对象声明语法的简化使程序员使用起来更简单。上述代码应改为: var T: TObject; begin T := TObject.Create; . T.Free; end; 上述两段代码并不相同,按照程序员对 C+的理解,它们不应该生成相同的机器码。但事实上,要是前一段代码在 Delphi 中能够编译

23、通过的话,它们的机器码是完全相同的。 union 和 struct 的区别union UTest double dlOne;char chOne; byte bArray4; ;好了,看到上面的定义,很像 struct 的定义,但是对于 union 来说,有几点是值得注意的:不能直接对其进行初始化;某个时候只能使用其中的一个元素;最后一点也是最重要的一点就是内存共享,分配给 union 内存的 Size 是其中 Size 最大的那个元素的Size。说到这里,既然 union 最重要的是内存共享,那么我们做如下定义:union UTest tEle; 然后赋值:tEle.dlOne = 2.0f

24、;现在是 dlOne 可用,下一步: tEle.chOne = A;到这里 dlOne 失去了其意义,chOne 变得可用。然后,我们再来看看 Struct,在 struct 中每一个元素都是分配内存的,而且都是有单独意义的,也就是说对一个变量的赋值并不影响其它变量的取值。到这里,各位应该明白这两者之间的区别了吧,事实上我个人认为,它们最主要的区别是在内存的分配和使用上。知道这一点,一切也就不难理解了。最后,在使用 union 的时候,可能有时候我们会来用其来对字节流进行分解和重组,这样使用的时候一定要注意各种内存对数据的存储,比如 Intel 是按高高低低的原则存储的,有些则是相反的。因此,

25、这点因该值得注意,否则得到的可能和预期的结果不一样。举例:使用 union 结构输出主机字节序int main ( void ) union short s; char csizeof(shor); un; un.s = 0x0102; printf ( “%s:“, CPU_VENDOR_OS ); if ( 2=sizeof(short) ) if ( 1=un.c0 else if ( 2=un.c0 else printf ( “unknownn“ ); else printf ( “sizeof(short) = %dn“, sizeof(short) ); return 0; 本文来自 CSDN 博客,转载请标明出处:http:/

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报