栈
由编译器自动分配释放管理。局部变量及每次函数调用时返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。 堆需要由程序员分配释放管理,若程序员不释放,程序结束时可能由OS回收。通常在堆中进行动态存储分配。
非初始化数据段
通常将此段称为b s s段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,未初始化的全局变量和静态变量存放在这里。在程序开始执行之前,内核将此段初始化为0。函数外的说明:long sum[1000] ; 使此变量存放在非初始化数据段中。
初始化的数据
通常将此段称为数据段,它包含了程序中需赋初值的变量。初始化的全局变量和静态变量存放在这里。例如,C程序中任何函数之外的说明:int maxcount = 99; 使此变量以初值存放在初始化数据段中。
正文段
C P U执行的机器指令部分。通常,正文段是可共享的,所以即使是经常环境指针环境表环境字符串执行的程序(如文本编辑程序、C编译程序、s h e l l等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改其自身的指令。
对于x86处理器上的Linux,正文段从0x08048000单元开始,栈底在0xC0000000之下开始(栈由高地址向低地址方向增长)。堆顶和栈底之间未用的虚拟空间很大。
Shell的size命令可以看到一个程序的正文段(text)、数据段(data)、非初始化数据段(bss)及文件长度.
[foxman@17:01:49 ]$size mydesign
text data bss dec hex filename
79210 1380 404 80994 13c62 mydesign
堆与栈的区别由以下几点:
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生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和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生,因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果.
来源:
1. 《UNIX环境高级编程》第3版
2. http://www.52blog.net/user1/3843/archives/2004/54340.shtml
:转载时请以超链接形式标明文章原始出处和作者信息及
注:
/*程序执行/函数调用过程:
函数调用时:
建立新栈帧,传递参数,修改“当前代码行”;
函数退出时:
删除栈帧,处理返回值,修改“当前代码行”
*/
过程调用
call 首先将被调函数的参数入栈,最后是返回地址入栈,再跳到被调函数起始地址leave 准备返回时的桢栈 : 令栈指针指向先指向当前桢的起始处(这里保存的是调用者桢的起始地),出栈(桢指针重置为调用者桢的起始;且栈指针指向返回地址)等同于 :
movl %ebp,%esp popl %ebpret (栈指针指向返回地址)出栈并跳到那个位置(返回地址).
程序栈的布局:
关于寄存器
caller save:%eax,%edx,%ecx
callee save:%ebx,%esi,%edi
因为caller负责保存了%eax,%edx,%ecx,所以callee才可以直接用.
而caller却不负责保存%ebx,%esi,%edi,故callee想用的话必须先将其保存,才能使用.
所谓使用,即对其覆盖它原来的值
所谓保存,即入栈
(src: )
段错误和栈溢出:
编译后的可执行文件都包含什么内容呢?对于Linux/Unix系统来说,其可性文件采用ELF格式,DOS系统采用COFF格式,Windows采用PE文件格式(COFF格式的扩展)。这些格式不尽相同,但它们都有一个共同的概念——段。
段(segmentation)是指二进制文件内的区域,所有特定类型的信息都被保存在里面。size程序可查看文件的段信息。
如: size a.exe
====> text data bss dec hex filename
2756 740 224 3720 e88 a.exe
a.exe由正文段,数据段,bss段共计3720字节组成。其中正文段(Text Segment)存储指令,数据段(Data Segment)存储已初始化全局变量,BSS(BSS segment)存储未赋值全局变量。
是不是觉得少了点什么东西呢?调用栈在哪里?调用栈并不存储在可执行文件中,而是在运行时动态创建,调用栈所在的段称之为 堆栈段 (Stack Segment),和其他段一样,他也有自己的大小,不能越界访问,否则将会出现段错误。无穷递归会不断的往调用栈里面加入栈帧,最后产生 栈溢出(Stack Overflow)。堆栈段在程序运行时动态创建,包含着调用栈,保存了函数调用关系和局部变量。
栈空间的大小因操作系统而异,Linux中,栈大小由系统命令ulimit指定,例如:ulimit -a显示当前栈的大小,ulimit -s 32768将栈的大小设置为32MB。 在Windows中,栈的大小存储在可执行文件中,使用gcc可以这样指定可执行文件的栈大小:gcc -Wl,--stack=<栈大小>。实际上,栈大小是由连接程序ld指定的,编译参数-Wl的作用就是将其后stack参数传递给ld。
以上,就可以理解为什么建议把较大的数组放在main函数的外面了:局部变量是放在堆栈段的。栈溢出不一定是函数调用层次太深,也可能是局部变量太大,只要是总大小超出了允许范围就会产生栈溢出。