C/C++内存模型

1、存储区域

1)Heap堆

由malloc分配的内存块,由程序员控制内存块的申请和释放(malloc/free)。如果申请的堆内存没有被释放掉,在程序结束时操作系统会自动回收。涉及问题:缓冲区溢出、内存泄漏。

2)Free store *存储区

由new分配的内存块。由程序员控制内存块的申请和释放(new/delete)。如果申请的堆内存没有被释放掉,在程序结束时操作系统会自动回收。涉及问题:缓冲区溢出、内存泄漏。

3)stack 栈

由编译器负责分配和清除内存存储区。存放具备变量、函数参数。存放在栈中的数据旨在当前函数及下一层函数中有效,一旦函数返回,这些数据占用的内存空间自动释放。

4).bss段和.data段 全局/静态存储区

全局变量和静态变量被分配到同一块内存中。在C语言中,未初始化的放在.bss段中,初始化的放在.data段中;在C++里不区分。

5).rodata段 常量存储区

存放常量,不允许修改(通过非正常手段可以达到修改目的)

6).text段 代码区

存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同常量存储区)

2、C/C++内存模型

C分为四个区:堆、栈、静态全局变量区、常量区。

C++分为五个区:堆、栈、静态全局变量区、常量区、*存储区。

根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即*存储区,动态区、静态区。

  • *存储区:局部非静态变量的存储区域,即平常所说的栈
  • 动态区: 用operator new ,malloc分配的内存,即平常所说的堆
  • 静态区:全局变量 静态变量 字符串常量存在位置

而代码虽然占内存,但不属于c/c++内存模型的一部分。

3、linux 中程序内存分布

C/C++内存模型

  • .text段:编译后程序的主体,即程序的机器指令。
  • .data段:保存程序已初始化的全局变量。
  • .bss段:保存只有声明而未初始化的全局变量。
  • heap:保存程序中动态分配的内存,比如C的malloc申请内存和C++ new申请的内存。堆向高地址方向增长。
  • stack:用来进行函数调用,保存函数参数、临时变量、返回地址等。

BSS是“Block Started by Symbol”的缩写,意为“以符号开始的块”,是Unix连接器产生的未初始化数据段,其它的段分别是包含代码的.text段和包含已初始化数据的.data段。BSS段的变量只有名称和大小但没有值。此名称被许多文件格式使用,包括PE。“以符号开始的块”指的是编译器处理未初始化数据的地方。BSS段不包含任何数据,只是简单的维护开始和结束地址,以便内存区能在运行时被有效的清零。BSS段在应用程序中的二进制镜像文件中并不存在。

在采用段式内存管理的架构中(如:intel的8086系统),BSS段通常用来存放程序中未初始化全局变量的一块内存区域,一般在初始化时,BSS段部分将会清零。BSS段属于静态内存分配,即程序一开始就将其清零了。

在C语言中之类的程序编译完成之后,已经初始化的全局变量保存在.data段中,未初始化的全局变量保存在.bss段中。

.text和.data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载。而.bss段不在可执行文件中加载,由系统初始化。

下图未可执行代码存储时结构和运行时结构对照:

C/C++内存模型

  • 代码区(text segment)。代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需 要借助栈来实现。代码区的指令中包括操作码和要操作的对象(或对象地址引用)。如果是立即数(即具体的数值,如5),将直接包含在代码中;如果是局部数据,将在栈区 分配空间,然后引用该数据地址;如果是BSS区和数据区,在代码中同样将引用该数据地址。
  • 全局初始化数据区/静态数据区(Data Segment)。只初始化一次。
  • 未初始化数据区(BSS)。在运行时改变其值。
  • 栈区(stack)。由编译器自动分配释放,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。每当一个函数被调用,该函数返回地址和一些关于调用的信息,比如某些寄存器的内容,被存储到栈区。然后这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,这就是C实现 函数递归调用的方法。每执行一次递归函数调用,一个新的栈框架就会被使用,这样这个新实例栈里的变量就不会和该函数的另一个实例栈里面的变量混淆。
  • 堆区(heap)。用于动态内存分配。堆在内存中位于bss区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS 回收。

之所以分成这么多个区域,主要基于以下考虑:

  • 一个进程在运行过程中,代码是根据流程依次执行的,只需要访问一次,当然跳转和递归有可能使代码执行多次,而数据一般都需要访问多次,因此单独开辟 空间以方便访问和节约空间。
  • 临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短。
  • 全局数据和静态数据有可能在整个程序执行过程中都需要访问,因此单独存储管理。
  • 堆区由用户*分配,以便管理。

用例:

int a = 0;    //a在全局已初始化数据区 
char *p1;    //p1在BSS区(未初始化全局变量) 
main() 
{
  int b;    //b在栈区
  char s[] = "abc"; //s为数组变量,存储在栈区,
  //"abc"为字符串常量,存储在已初始化数据区
  char *p1,p2;  //p1、p2在栈区
  char *p3 = "123456"; //123456\0在已初始化数据区,p3在栈区 
  static int c =0;  //C为全局(静态)数据,存在于已初始化数据区
  //另外,静态数据会自动初始化
  p1 = (char *)malloc(10);//分配得来的10个字节的区域在堆区
  p2 = (char *)malloc(20);//分配得来的20个字节的区域在堆区
  free(p1);
  free(p2);
}

 

上一篇:CentOS-7.6 命令及配置


下一篇:iOS进阶收藏