设为首页 加入收藏

TOP

C++内存分区模型(一)
2023-07-26 08:17:25 】 浏览:205
Tags:

C++内存分区模型

在执行C++程序的过程中,内存大致分为四个区域:

  1. 栈区(Stack):用于实现函数调用。由编译器自动分配释放,存放函数的参数值和局部变量等

  2. 堆区(Heap):用于存放动态分配的变量。由程序员动态分配和释放,使用new和delete操作符

  3. 全局/静态存储区(Data Segment & BSS Segment):存放全局变量和静态变量,程序结束时释放

    数据段 Data Segment (全局/静态存储区) : 存放初始化了的全局变量和静态变量

    BSS段 BSS Segment : 用于存放未初始化的全局变量和静态变量,节省空间,实际上不占用磁盘空间

  4. 代码区(Text Segment):通常也被称为文本区或只读区。存放程序的二进制代码和常量,代码段是只读的,可以被多个进程共享

也有人认为 常量存储区 在内存中是独立的,C++标准并没有明确将常量存储区单独列为内存分区模型的一部分。因此,内存分区模型的确切细节可以根据不同的观点和上下文而有所不同

注意:不同的操作系统对程序内存的管理和划分会有所不同。上述的C++内存区域划分主要是针对通用的情况,并不限定在某个特定操作系统上

1. 代码区

代码区 (Code Segment) 也被称为文本段 (Text Segment) 或者只读区,主要包括可以执行的文件 ELF (Executable and Linkable Format) 和常量

代码区(Code Segment)也被称为文本段(Text Segment)或只读区,它有以下几个主要特征:

  1. 代码区是程序的静态存储区域,存放程序执行所需的机器指令和常量数据,如字符串字面量和 const 变量的初始化值
  2. 代码区的内容为只读属性,不允许修改程序中的代码,保障程序的安全性和稳定性
  3. 程序运行前,代码区的内存大小已在可执行文件中确定,加载时系统会自动分配适当的内存
  4. 多个进程可以共享相同的代码区,节省内存空间
  5. 为提高效率,一些字符串字面量 (例如 "Hello") 可能会存放在共享的只读数据区而不是代码区
  6. 若代码区中的常量初始化需要运行时计算,会将其放在数据区而不是代码区
  7. 代码区也包含只读数据,如跳转表和常量表等
  8. 程序运行时,代码区通常不会改变大小,若需扩展,依赖操作系统提供的机制

2. 全局/静态存储区

全局/静态存储区主要包括以下两个部分:

  1. 数据段(Initialized Data Segment)

    • 用于存放初始化了的全局变量和静态变量

    • 存储在此段的数据在程序运行前分配,运行结束后释放

    • 有初始化值的全局变量和静态变量存放在此

    • 数据段属于可读可写区域

  2. BSS段(Block Started by Symbol)

    • 用于存放未初始化的全局变量和静态变量

    • 不占用实际的磁盘空间,只在编译时预留内存空间

    • 无初始化值的全局变量和静态变量存放在此

    • 程序启动时会自动初始化为默认值

    • 属于可读写区域

两者的主要区别在于初始化状况。全局变量和静态变量可以显式初始化,如果没有显式初始化,它们会被自动初始化为默认值(0 或 nullptr,取决于变量的类型),该区域的数据在程序整个运行周期中一直存在

3. 栈区

栈区是用于实现函数调用和局部变量存储的一种内存区域。在 C++ 中,每当调用一个函数时,系统会自动在栈区为该函数分配一块内存,称为栈帧(Stack Frame)。栈帧用于存储函数的参数值、局部变量以及函数执行期间的一些控制信息 ^f6a053

以下是栈区的一些关键特点:

  1. LIFO(Last-In-First-Out)原则:栈区采用后进先出的原则,即最后压入栈的数据会最先弹出。这是因为每次调用函数时,会将函数的栈帧压入栈顶,函数执行结束后,栈帧会从栈顶弹出
  2. 自动分配和释放:栈区的内存分配和释放是由编译器自动管理的,当进入函数时,会为该函数分配一块连续的内存区域,并在函数返回时自动释放这块内存。这样的自动分配和释放使得栈区的内存管理相对高效,但也意味着栈上的数据生命周期必须在函数调用内部
  3. 局部变量存储:栈区主要用于存储函数的局部变量,这些变量的生命周期与函数的调用和返回相对应。当函数调用结束,栈帧会被销毁,其中的局部变量也会被销毁,因此在函数外部无法访问这些局部变量
  4. 函数调用:当调用函数时,函数的参数值和返回地址会被压入栈帧中,函数执行过程中的其他局部变量也会存储在栈帧中。函数返回时,栈帧会从栈顶弹出,恢复调用函数的现场
  5. 栈溢出:栈区的大小是有限的,如果递归调用过深或者函数中使用了大量的局部变量,可能导致栈溢出(Stack Overflow)错误,即栈区的内存已被耗尽
  6. 线程私有:每个线程都有自己的栈区,栈区的内存是线程私有的,不同线程之间的栈区不共享

3.1 栈溢出

栈溢出(Stack Overflow)是指程序在运行过程中,不断调用函数,导致栈空间被占满,无法再分配新的栈帧。栈是一种先进后出的数据结构,用于存储局部变量、参数、返回地址等信息。栈的大小是有限的,一般为8~10MB。栈溢出通常发生在递归调用过深或死循环的情况下

以下是一个程序在栈区的示例图。在主函数调用自定义的函数,函数内部形成递归

img

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

img

3.2 缓冲区溢出

在C++中,缓冲区溢出通常发生在栈区。栈区用于存储函数调用时的局部变量和函数调用的返回地址

当函数调用时,函数的栈帧(包含局部变量和其他控制信息)被压入栈中。如果函数中使用了缓冲区(数组)并且没有进行足够的边界检查,很容易导致写入超出缓冲区边界的数据,从而覆盖栈上其他变量和控制信息,这就是缓冲区溢出

例如以下代码就是一个缓冲区溢出的案例:

#include <iostream>

int main()
{
    //创建并初始化数组
    char arr[3] = {'a', 'a', 'a'};

    //向数组写入数据,但超出了数组的边界
    for (int i = 0; i <= 4; i++)
    {
        arr[i] = 'b';
    }
}

我们在合适的位置添加调试断点,并对数组arr添加监视,观察内存变化

img

找到数组对应的地址,观察内存中的变化

img

按下F11逐语句调试,发现数组越界后仍然向未被允许访问的内存中写入数据

img

显示数组所申请的内存附近堆栈被损坏,这就是缓冲区溢出的危害

img

参考:缓冲区溢出原理——作者:程序员老马

3.3 数据不稳定

栈区中的局部变量生命周期与函数的调用和返回相关。当函数返回后,栈帧会被销毁,其中的局部变量也会被销毁。如果在函数内部使用了指向栈区局部变量的指针,并在函数返回后继续使用这些指针,会导致悬空指针(Dangling Pointers)也就是野指针的问题,访问已被销毁的局部变量的内存区域,可能引发未定义的行为

下面是示例代码,证明栈帧销毁后局部变量也被销毁,造成悬挂指针的问题:

#include <iostream>

int* dangPtr;

void test() {

	int x = 10;
	dangPtr = &x;

	std::cout << "dangPtr in test: " << *dangPtr << std::endl;
}

int main() {

	test();
	std::co
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Lucas定理 下一篇Acwing 327. 玉米田

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目