【编程基础】堆空间与栈空间

在 C 语言中,内存分布的部分情况如下图所示:

【编程基础】堆空间与栈空间_第1张图片

有些部分并没有在图中表示出来,实际上内存分布的功能划分从高地址到低地址依次是:

  • 内核空间:应用程序不允许访问的部分,只能由内核进行操作,操作系统的内核程序映射到这个区域。
  • 栈空间:保存局部变量。
  • 文件映射区:进程打开文件后,将文件内容从硬盘读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完成后保存时,再将内存中的文件写入硬盘中。
  • 堆空间:运行时使用,常用 malloc( ) / free( ) 或者 new( ) / delete( )。
  • 全局的数据空间:包括 data 段和 bss 段
    • data 段:保存初始化为非 0 的全局变量、static 修饰的局部变量。
    • bss 段:保存未初始化或者初始化为 0 的全局变量,static 修饰的局部变量。
  • 只读数据段(rodata):又称文字常量区。用于存储常量数据,例如常量字符串”Hello world"。
  • 代码段:又称程序代码区。存放只读代码、函数等。

当一个局部变量用 static 进行修饰时,作用范围还是在当前函数内部,但是在函数释放后,并没有释放,该变量将进入静态数据段中的全局数据空间。

与存储相关的关键字声明包括:

  • auto:修饰局部变量,默认不写,分配在栈上,不初始化则为随机值(该地址原有的值)。

  • static:形成静态局部变量或者静态全局变量,存储在全局数据空间。

    • static + 函数:限定函数作用域,只有在本文件内可调用,在别的文件里不可见。
    • static + 局部变量:限定声明周期,所在函数释放后该变量的作用域不释放,其值仍在内存空间中维持。
    • static + 全局变量:限定作用域,只有在本文件内可访问,在别的文件里不可见。
  • register:编译器会尽量将修饰的变量分配在寄存器中,变为寄存器变量,提高访问效率。

  • extern:修饰全局变量,可实现跨文件访问。

  • volatile:修饰可能会被意想不到改变的变量,编译器不会去优化这个变量的值,在获取变量值时,不再是直接使用保存在寄存器里的备份,而是以某种形式重新读取正确的值。常见的使用场景有:

    • 某些硬件(状态寄存器)改变变量的值;
    • 中断子程序访问到的非自动变量(non-auto variables);
    • 多线程应用中被共享的变量。

结合以上的介绍,在文件水平上,数据的连接属性有三种:

  • 无连接:所有的局部变量。
  • 内连接:static 修饰的函数、全局变量
  • 外连接:extern 修饰的函数、局部变量

堆空间

堆空间用于存储和动态分配的内存块,一般由程序员负责分配和释放空间,使用 malloc( ) / free( ) 或者 new( ) / delete( )。如果程序员没有释放内存空间的话,程序结束时系统会进行回收,但也有可能会产生内存泄露。

堆在不断地分配和释放空间的过程中,可用的内存空间更新频繁,这就会造成内存空间逐渐碎片化,留下大量较小的可用空间。

堆空间理论上的大小有几 G 的空间,生长方式是向上的,也就是向着内存地址增大的方向消耗空间。虽然堆空间的内存地址生长方向是向上的,但是后申请的内存空间并不一定在先申请的内存空间的后面,因为堆空间的内存块是动态分配的,后申请的内存空间也有可能是由之前申请的内存空间释放后得到的。

堆空间通过函数动态获取空间,涉及到可用空间链表的扫描和调整,以及相邻可用空间的合并等操作,效率相对较低。其空间分配方式类似于数据结构的链表。在操作系统中有一个记录空闲内存的链表,当系统收到程序申请时,会遍历该链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。对于大多数系统而言,会在这块内存空间的首地址处记录下本次分配的大小,通过这种方式,执行 delete 代码时才能正确释放该内存空间。由于找到的堆结点的大小并不一定刚好等于申请的大小,系统会自动将多余的部分重新放入堆空间链表中。

栈空间

栈空间用于存储函数参数和局部变量,包括各种基本类型的变量和对象的引用变量。所需空间由系统自动分配,回收也由系统管理,不需要人为干预。栈中存储数据的生命周期随着函数的执行完成而结束。

栈空间在使用时,遵循后进先出的原则,可用空间永远都是一块连续的内存空间,栈顶和栈的最大容量都是系统预先规定好的。只要栈的剩余空间大于所需要分配的内存空间,那么系统将为程序提供内存,否则将会报异常提示栈溢出。

栈空间默认只有几 M 的空间,生长方式是向下的,也就是向着内存地址减小的方向消耗空间。

栈空间有计算机底层的支持,压栈和出栈都有专门的指令,效率较高。

堆与栈对比

区别方面 堆空间 栈空间
管理方式 程序员手动申请和释放内存空间,程序结束时系统可进行回收,但容易产生内存泄露。 操作系统自动分配和释放内存空间。
空间大小 几个 G。 一般只有 8 - 10 M。
生长方向 向上,内存地址由低到高。 向下,内存地址由高到低。
分配方式 动态分配,如 malloc 函数,需要手动释放内存。 静态分配、动态分配。局部变量的分配属于静态分配,由操作系统完成;动态分配由操作系统进行释放,不需要手动释放。
分配效率 效率低,内存申请与管理机制比较复杂,频繁的内存申请容易产生内存碎片。 效率高,分配过程由操作系统自动分配,硬件提供支持,分配专门的寄存器存放栈的地址,压栈和出栈都有专门的指令。

实际应用环境

  • 当在一段代码中定义一个变量时,系统就会在栈中为这个变量分配内存空间,当超过变量的作用域后,会自动释放掉这部分内存空间,该内存空间可以立即分配给新的变量。

  • 当程序员 new 一个对象时(引用对象),在堆空间中会进行对象初始化空间的分配,如成员变量、成员方法等;在栈空间中保留一个该对象的引用地址

  • 数组从栈中分配空间,链表从堆中分配空间

  • 有时候需要分配大量内存空间的时候,仍然需要优先使用堆。

你可能感兴趣的:(编程基础,操作系统,堆空间,栈空间,内存空间,动态分配)