`
蒙面考拉
  • 浏览: 156131 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

内存分配---堆与栈

 
阅读更多

1.堆和栈的区别

          1、管理方式不同;  
          2、空间大小不同;  
          3、能否产生碎片不同;  
          4、生长方向不同;  
          5、分配方式不同;  
          6、分配效率不同;  
          管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory   leak。  
          空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:          
          打开工程,依次操作菜单如下:Project->Setting->Link,在Category   中选中Output,然后在Reserve中设定堆栈的最大值和commit。  
  注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。  
          碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。  
          生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。  
          分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。  
          分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。  
          从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。  
          虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。  
          无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)

 堆就是,取和放数据没有顺序,想拿哪个拿哪个 栈则不同,先进后出,  从实现角度讲,栈往往用来用于函数的参数和返回值的   传递,在函数退出后,会被pop空,速度很快,就不是于用   长时间的变量;  所以在函数内部的变量,在函数退出后,就被释放掉了;  而如果new的则不同,会放在堆里,函数对出后也不会释放。

2.堆和栈详细解释:  
   
  堆栈:线程使用的临时数据存放区。早期的操作系统堆栈依赖于进程而不是线程。windows中堆栈依赖于线程,每一个线程都有独立的堆栈,但是独立的含义不意味着堆栈的地址空间不是在进程中的,他仍然在线程依赖的进程地址空间中分配。windows的纤程使用线程的堆栈。线程切换的时候切换堆栈寄存器,这是自动进行的,不需要应用程序员考虑如何切换,系统程序员需要考虑如何切换,通常,是改变堆栈寄存器使用的门描述符表。  
   
  分配堆栈空间:堆栈在线程初始化的时候分配空间,通常继承创建此线程的进程的缺省堆栈规格。这不需要应用程序员干预,通常是设置进程初始化的参数,来改变堆栈的缺省尺寸,在程序运行中不能改变堆栈尺寸。  
   
  windows堆和堆栈的区别:堆是和进程相关联的,它是windows为进程预留的地址空间,特别提示:预留的地址空间并不意味着你已经获得了这些空间,在没有真正分配之前你是不能使用的,windows使用两步提交来分配内存。使用堆函数来管理堆。堆又称为全局堆,在windows95的时候,所有进程的堆是在一起的,但是,windows98以后,堆不再是交叉的,而是进程隔离的,全局堆沿用了老的说法。  
   
  堆栈的使用:如何使用堆栈通常是编译器的工作,大多数的编译器将函数的内部变量存放在堆栈中,函数的参数通常也是通过堆栈来传递,堆栈传递参数,通常是复制传送,传地址参数并不进行堆栈复制传送。调用函数的时候,编译器一般生成移动堆栈栈顶寄存器的代码,上述过程,高级语言程序员是不可见的,汇编语言程序员通常需要关心。  
   
  关于Delphi的堆栈使用:Delphi将全局变量、全局常数和对象类型变量存放在堆当中,将局部变量、常数、简单结构变量存放在堆栈。对象类型的变量的含义是指:类的实例、具有类性质的默认类型(比如string类型)。但是对象的引用变量,如果是局部变量,仍然存放在堆栈,这样的变量相当于指针。结构变量,在堆栈中分配。在Delphi中没有任何函数可以在堆栈中分配空间。  
   
  关于C++的堆栈使用:C++中除非你指出了局部变量在堆上面分配,那么所有的局部变量都是在堆栈分配空间的。  
   
  堆栈溢出:堆栈的空间分配完了,直观的就是栈顶寄存器的值超过了堆栈寄存器和栈长度-1之和。造成这种情况,通常是递归函数没有返回。一般的,我们的程序不可能由于函数调用深度过大造成堆栈溢出。在C当中这种情况出现的可能要高于Delphi,因为C++会在堆栈上面分配大尺寸的对象。还有一种情况,程序莫名其妙的改变了栈顶寄存器的值,这种情况是可能的,函数返回的时候需要根据保存在堆栈中的原先栈顶寄存器的值来恢复上一级函数的堆栈引用,但是在这个函数中很可能因为错误而修改了这个值。那么再返回的时候就会溢出。  
   
  堆栈溢出和缓冲区溢出的差异:缓冲区溢出是指为变量分配的空间小于实际使用的空间。最常见的就是字符串溢出。我们在函数中为字符串变量分配了一段固定长度的空间,但是,函数调用者可能传递了一个超过我们预留长度的字符串,如果我们没有检查传递过来的字符串长度就拷贝到我们的局部变量当中,那么就会破坏紧跟我们字符串变量其他局部变量的内容,缓冲溢出攻击的常见手段就是破坏内容一直到函数的结尾,函数在ret的时候需要使用堆栈中保留的返回地址,这个地址如果被修改成了攻击者希望返回的地址,那么攻击者就可以执行他想执行的任何代码,不过这个手段不是很容易实现的。还有一种比较容易的手段,就是造成异常,从而控制程序,异常的机制我不是很清楚,所以不好讨论。Delphi中字符串都分配在堆当中,所以即使发生溢出,也是和代码段隔离的很好,不会发生问题,C++的程序通常很难避免

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics