Linux运行中的C程序的内存空间布局:
-
代码段(.text segment)
存放程序执行代码的一块内存区域,该区域大小在程序运行前就已经确定,通常属于只读。 -
初始化数据段(.data segment) (图中有误)
通常用来存放已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 -
未初始化的数据段(.bss segment)
通常用来存放未初始化的全局变量的一块内存区域。 -
堆(heap)
用来存放进程运行中被动态分配的内存段,大小并不固定。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上或释放的内存从堆中被剔除。 -
栈(stack)
又称堆栈,存放程序的局部变量(但不包括static声明的变量,static在数据段中存放变量)。除此之外,函数被调用时,栈用来传递参数和返回值。由于栈FILO特点,所以栈特别方便用于保存/恢复调用现场。
堆栈的区别:
-
申请方式不同
栈:由系统自动分配。
堆:需要程序员自己申请,并指明大小。注意动态分配的指针本身在栈中,但是分配的区域在堆内。 -
申请后系统的相应不同
栈:只要栈剩余空间大于申请空间,系统将提供内存,否则报异常,提示栈溢出。
堆:OS有一个记录空闲内存地址的链表,当系统收到程序申请时会遍历该链表,寻找第一个空间中大于所申请空间的堆结点,将其从空闲链表中删除,并分配给程序。在这块内存空间中的首地址处记录本次分配的大小,这样delete才能正确释放。最后,由于找到的堆结点大小不一定等于申请大小,系统会自动将多余部分重新放回空闲链表中。 -
申请大小的限制不同
栈:栈是向低地址扩展的数据结构,是一块连续的内存区域。用ulmit -a命令可以看到栈大小的限制。可以通过ulimit -s修改栈的大小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址,而链表的遍历方向是由低地址向高地址。 -
申请效率不同
栈:栈由系统自动分配,速度较快,但程序员无法控制。
堆:堆由new分配,速度比较慢,容易产生内存碎片。 -
存储内容不同
栈:在大多数的C编译器中,在函数调用时,第一个进栈的是当前函数后面的下一条指令的地址,然后是函数的各个参数(参数从右往左入栈,这是为了快速恢复),然后是函数中的局部变量。静态变量不入栈。当本次函数调用结束后,局部变量、参数、函数下一条指令依次出栈,程序由该点继续运行。
堆:一般在堆头部用一个字节存放堆的大小,具体内容由程序员安排。
实例说明内存空间布局:
源代码:
程序运行结果:
- 先分析全局变量的存储情况
按照调用的顺序为全局变量g1,g2,g3,main中静态变量s1,s2,s3,以及max中静态变量n1_max,n2_max,n3_max。依次进行连续的静态内存分配,由低地址向高地址:
- 分析局部变量 的存储情况
首先是main函数,在函数调用时,依次将函数地址,参数地址,以及变量入栈。需要注意的是,由于栈是向下扩展的,栈顶在上,栈底在下,新进来的数据地址逐渐变小:
然后是max函数:
通过比较main函数以及max函数也可以发现它们在栈中的相对位置。
需要特别注意栈是向低地址扩展的:堆栈向下扩展的说明(注意该博客中的图内存与本博客中相反)
- 动态内存的分配:
与数据段中向高地址分配的方式类似。
- 函数运行代码的分配
函数的地址相当于代码段,根据源码的顺序进行分配,因此也是向高地址扩展的。