光有栈对于面向过程的程序设计还远远不够,因为栈上的数据在函数返回的时候就会被释放掉,所以无法将数据传递到函数外部.而全局变量没办法动态的产生,只能在编译的时候定义,缺乏表现力.在这种情况下,堆是唯一的选择.
堆是一块巨大的内存空间,常常占据整个虚拟空间的绝大部分.在这片空间里,程序可以请求一块连续内存,并*使用,这块内存在主动释放前都一直有效.
通过malloc可以为程序申请分配堆空间.但是管理对空间的往往是运行库.运行库向操作系统申请一块较大的堆空间,然后吧申请到的空间给程序使用.当申请到的空间不够时再由运行库向操作系统申请.运行库必须管理它申请到的堆空间,不能把同一块地址空间两次给程序使用,导致地址冲突.于是运行库需要一个算法来管理堆空间,这个算法就是堆的分配算法. 在调用malloc函数时,如果之前运行库向系统申请到的堆空间还够用,那么直接在已经申请到而未使用的堆空间中取,如果不够用了,就通过系统调用或者API 向操作系统申请.
malloc分配的堆空间是否连续? 对于虚拟空间,每一次malloc分诶后返回的空间都可以看做是一块连续的地址;对于物理空间,则不一定是连续的,因为一块连续的的虚拟空间有可能是有若干个不连续的物理页面拼凑而成的.
一个进程的地址空间中,除了可执行文件,共享库,栈之外,剩余的未分配的空间都可以作为堆空间.Linux提供了两种分配堆空间的方式,即两个系统调用,一个是brk() ,另一个是nmap();
int brk( void * end_data_segment )
brk() 的作用实际上就是设置进程数据段的结束地址,即它可以扩大或者缩小数据段(Linux下数据段和BSS合并在一起称为数据段).如果我们将数据段的结束地址想高地址移动,那么扩大的那部分空间就可以被我们使用.把这块空间拿出来作为堆空间是最常见的常见做法之一.Glibc中还有一个函数叫做sbrk() ,它的功能与brk类似,只不过参数和返回值不同,sbrk以增量作为参数,返回数据段的结束地址,sbrk函数是对brk系统调用的包装,它是通过调用brk()实现的.
mmap() 的作用是向操作系统申请一段虚拟地址空间,然后将这块虚拟地址空间映射到某个文件.当它不降地址空间映射到某个文件是,这块空间为匿名空间,匿名空间可以拿来作为堆空间;
void * nmap(
void * start,
size_t length,
int port,
int flags,
int fd,
off_t offset
);
mmap() 函数虚拟地址空间申请函数,它申请的空间的其实地址和大小都必须是是系统页大小的整倍数.对于小的请求如果也使用mmap的话,会浪费很大的空间.
在有共享库的情况下,留给对可以分配的空间哟两处.第一处就是从bss段结束到共享库的装载地址0x40000000(注:Linux内核2.6版本里,共享库的装载地址已经被挪到了靠经栈的位置,即位于0xbfxxxxxx附近),即大约1GB的空间;第二出是从共享库到栈的这块空间(注:如果使用静态链接来产生可执行文件,共享库就不会占据程序的虚拟空间了),大约是2GB.这两块空间的大小都取决于栈,共享库的大小和数量.
更多的内容请参考<<程序员的自我修养>> .这本书中有对堆详细介绍的部分.