C语言之内存四区

​ C语言编译执行在CPU上的时候,其内存占用主要可以分为四个区域:栈区、堆区、全局区、代码区,这四个区域有着不同的存储特性和存储位置,下面一一介绍;

栈区

​ 栈区是RAM里面的一段,主要用于临时存放函数的参数值、局部变量值,该内存的分配过程由编译器进行分配,当我们在main中调用一个函数 fun() 的时候,编译器会将main函数的运行数据进行压栈做现场保护,保存main函数运行时对应的寄存器值以及main函数的返回地址到堆栈,然后将fun的参数进行压栈,开始运行函数程序,当fun()函数执行完成之后,编译器会进行出栈操作,弹出当前函数的参数值(局部变量的生命周期就是从压栈到出栈的这段时间),当SP指针(栈顶指针)指向返回地址时,会把参数赋值给PC(程序计数器),程序跳转到原先main中调用的地方,然后SP继续向下走,弹出main函数的运行状态到寄存器里面,之后main函数继续运行程序,这就是栈区的运行流程,运行流程就如下图:

内存四区 (2)

​ 由上面的描述我们可以明白栈区有如下特点:

  1. 就是RAM里面一个存储空间,其生长方向由高地址向低地址生长(到底后就不能继续生长,内存有限),有一个专门的栈顶指针(SP)指向栈顶
  2. 内存分配的操作由编译器来进行,程序自动向操作系统申请分配以及回收,速度快且使用方便
  3. 栈区通常用来存储局部变量函数参数以及函数调用后返回的地址

堆区

​ 堆区也是RAM里面的一段,一般由程序员手动分配释放(比如使用C语言内的malloc和free动态内存分配),调用函数后会向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,若程序员不释放,程序结束时可能由操作系统回收,如果因为其他原因没有及时释放,程序结束后,没有指针指向这一块内存,则会导致内存泄漏

内存泄漏是指程序中动态分配的的堆内存,由于某些原因无法释放或者未释放,造成的内存浪费

堆区的特点:

  1. 由程序员来进行分配,灵活性大,但其分配的速度较慢,地址不连续,容易碎片化
  2. 地址生长方向由低地址往高地址,申请上限由计算机自身的虚拟内存来决定

虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为连续的虚拟内存地址,以借此「欺骗」程序,使它们以为自己正在使用一大块的连续地址,对程序来说它是连续的,完整的,实际上虚拟内存是映射在多个物理内存碎片上,还有部分映射到了外部磁盘存储器上。虚拟内存有以下两个优点:

1.虚拟内存地址空间是连续的,没有碎片

2.虚拟内存的最大空间就是CPU的最大寻址空间,不受内存大小的限制,能提供比内存更大的地址空间

全局区(静态区)

全局区用于存放全局变量静态变量以及常量,变量中初始化过的变量 ** 和 **未初始化的变量或者初始化为0的变量放在不同区域:

  1. 初始化的全局变量和静态变量在一块区域
  2. 未初始化的全局变量和未初始化的静态变量或者初始化为0的全局\静态变量在相邻的另一块区域
  3. 常量如字符串常量,const声明后的变量存放在常量区

Tips:两种变量为什么要分开放呢?

此处主要涉及到程序三段概念:程序三段分为如下三段:

  1. 代码段(.text段)

  2. 数据段(.data段)

  3. .bbs段

每一段的功能如下:

代码段:存放指令代码(局部变量也放在代码段),只读

数据段:存放初始化值不为0的全局变量或静态变量,可写

BSS段:存放初始化为0或未初始化的全局或静态变量,可写

可以看到程序三段和内存四区有一定的对应关系的,初始时启动代码会加载内存四区的数据,为了让启动代码更加简单快速,编译链接器会直接按照分区读取数据到对应的程序段,比如直接复制全局区的初始化变量到数据段,或者直接将未初始化段的区域直接清零后读取到.bbs段,简化启动流程

注意:程序三段是CPU级别的概念,内存四区是C语言级别的概念


代码区

代码区用于存放机器指令,代码区是可共享的(即另外的执行程序可以调用它),主要为了程序可复用,不用同样的程序放几份,代码区通常是只读的,主要防止指令被程序意外修改

wechat