C++内存分区模型
在执行C++程序的过程中,内存大致分为四个区域:
- 栈区(Stack):用于实现函数调用。由编译器自动分配释放,存放函数的参数值和局部变量等
- 堆区(Heap):用于存放动态分配的变量。由程序员动态分配和释放,使用new和delete操作符
- 全局/静态存储区(Data Segment & BSS Segment):存放全局变量和静态变量,程序结束时释放
数据段 Data Segment (全局/静态存储区) : 存放初始化了的全局变量和静态变量
BSS段 BSS Segment : 用于存放未初始化的全局变量和静态变量,节省空间,实际上不占用磁盘空间
- 代码区(Text Segment):通常也被称为文本区或只读区。存放程序的二进制代码和常量,代码段是只读的,可以被多个进程共享
也有人认为 常量存储区 在内存中是独立的,C++标准并没有明确将常量存储区单独列为内存分区模型的一部分。因此,内存分区模型的确切细节可以根据不同的观点和上下文而有所不同
注意:不同的操作系统对程序内存的管理和划分会有所不同。上述的C++内存区域划分主要是针对通用的情况,并不限定在某个特定操作系统上
1. 代码区
代码区 (Code Segment) 也被称为文本段 (Text Segment) 或者只读区,主要包括可以执行的文件 ELF (Executable and Linkable Format) 和常量
代码区(Code Segment)也被称为文本段(Text Segment)或只读区,它有以下几个主要特征:
- 代码区是程序的静态存储区域,存放程序执行所需的机器指令和常量数据,如字符串字面量和 const 变量的初始化值
- 代码区的内容为只读属性,不允许修改程序中的代码,保障程序的安全性和稳定性
- 程序运行前,代码区的内存大小已在可执行文件中确定,加载时系统会自动分配适当的内存
- 多个进程可以共享相同的代码区,节省内存空间
- 为提高效率,一些字符串字面量 (例如 "Hello") 可能会存放在共享的只读数据区而不是代码区
- 若代码区中的常量初始化需要运行时计算,会将其放在数据区而不是代码区
- 代码区也包含只读数据,如跳转表和常量表等
- 程序运行时,代码区通常不会改变大小,若需扩展,依赖操作系统提供的机制
2. 全局/静态存储区
全局/静态存储区主要包括以下两个部分:
- 数据段(Initialized Data Segment)
- 用于存放初始化了的全局变量和静态变量
- 存储在此段的数据在程序运行前分配,运行结束后释放
- 有初始化值的全局变量和静态变量存放在此
- 数据段属于可读可写区域
- BSS段(Block Started by Symbol)
- 用于存放未初始化的全局变量和静态变量
- 不占用实际的磁盘空间,只在编译时预留内存空间
- 无初始化值的全局变量和静态变量存放在此
- 程序启动时会自动初始化为默认值
- 属于可读写区域
两者的主要区别在于初始化状况。全局变量和静态变量可以显式初始化,如果没有显式初始化,它们会被自动初始化为默认值(0 或 nullptr,取决于变量的类型),该区域的数据在程序整个运行周期中一直存在
3. 栈区
栈区是用于实现函数调用和局部变量存储的一种内存区域。在 C++ 中,每当调用一个函数时,系统会自动在栈区为该函数分配一块内存,称为栈帧(Stack Frame)。栈帧用于存储函数的参数值、局部变量以及函数执行期间的一些控制信息 ^f6a053
以下是栈区的一些关键特点:
- LIFO(Last-In-First-Out)原则:栈区采用后进先出的原则,即最后压入栈的数据会最先弹出。这是因为每次调用函数时,会将函数的栈帧压入栈顶,函数执行结束后,栈帧会从栈顶弹出
- 自动分配和释放:栈区的内存分配和释放是由编译器自动管理的,当进入函数时,会为该函数分配一块连续的内存区域,并在函数返回时自动释放这块内存。这样的自动分配和释放使得栈区的内存管理相对高效,但也意味着栈上的数据生命周期必须在函数调用内部
- 局部变量存储:栈区主要用于存储函数的局部变量,这些变量的生命周期与函数的调用和返回相对应。当函数调用结束,栈帧会被销毁,其中的局部变量也会被销毁,因此在函数外部无法访问这些局部变量
- 函数调用:当调用函数时,函数的参数值和返回地址会被压入栈帧中,函数执行过程中的其他局部变量也会存储在栈帧中。函数返回时,栈帧会从栈顶弹出,恢复调用函数的现场
- 栈溢出:栈区的大小是有限的,如果递归调用过深或者函数中使用了大量的局部变量,可能导致栈溢出(Stack Overflow)错误,即栈区的内存已被耗尽
- 线程私有:每个线程都有自己的栈区,栈区的内存是线程私有的,不同线程之间的栈区不共享
3.1 栈溢出
栈溢出(Stack Overflow)是指程序在运行过程中,不断调用函数,导致栈空间被占满,无法再分配新的栈帧。栈是一种先进后出的数据结构,用于存储局部变量、参数、返回地址等信息。栈的大小是有限的,一般为8~10MB。栈溢出通常发生在递归调用过深或死循环的情况下
以下是一个程序在栈区的示例图。在主函数调用自定义的函数,函数内部形成递归

当程序一直运行时,递归的函数会不断占用栈区的内存空间,从而导致 栈溢出

3.2 缓冲区溢出
在C++中,缓冲区溢出通常发生在栈区。栈区用于存储函数调用时的局部变量和函数调用的返回地址
当函数调用时,函数的栈帧(包含局部变量和其他控制信息)被压入栈中。如果函数中使用了缓冲区(数组)并且没有进行足够的边界检查,很容易导致写入超出缓冲区边界的数据,从而覆盖栈上其他变量和控制信息,这就是缓冲区溢出
例如以下代码就是一个缓冲区溢出的案例:
[code]#include int main(){ //创建并初始化数组 char arr[3] = {'a', 'a', 'a'}; //向数组写入数据,但超出了数组的边界 for (int i = 0; i |