文章

C++内存分区

代码区存放程序代码,数据区存全局变量,堆区动态分配内存,栈区存函数局部变量。

C++内存分区

C++内存分区

进程地址空间是指进程能够访问的虚拟地址范围。在大多数操作系统中(如 Linux、Windows 等),进程地址空间被划分为两个主要部分:

  1. 用户态(用户区)
  2. 内核态(内核区)

以 32 位 Linux 为例(3G/1G 分布):

区域地址范围所属空间说明
用户区0x00000000 ~ 0xBFFFFFFF用户态上面图里的部分
内核区0xC0000000 ~ 0xFFFFFFFF内核态所有进程共享,进程无法访问

内核区是操作系统在每个进程虚拟地址空间中保留的一块地址范围,用来存放内核代码、数据、驱动程序和关键内核结构(如进程控制块PCB)。这部分地址对所有进程共享映射,但普通用户态进程无法访问,只有进入内核态时才能操作,保证系统安全和资源管理统一。

以下只讨论用户区。

内存结构图示意

+-------------------------+
| 栈 Stack                  | <--- 高地址                             |
| ------------------------- |
| 空间 (可能是库)           |
| ------------------------- |
| 堆 Heap                   | <--- malloc/new分配从低地址往高地址扩展 |
| ------------------------- |
| BSS(未初始化全局变量)   |
| ------------------------- |
| Data(已初始化全局/静态) |
| ------------------------- |
| Text(代码区)            | <--- 低地址                             |
+-------------------------+

这个结构描述的是 一个 C/C++ 程序在进程中的典型虚拟内存布局(即进程地址空间结构)。这是一种逻辑视图,不是物理内存,而是操作系统为每个进程划分的虚拟地址空间

1. 代码区(Text Segment)

  • 内容:程序的机器指令(即编译后的可执行代码)。

  • 特点

    • 通常是只读的,防止程序意外修改指令。

    • 多个相同程序的进程之间可以共享这块区域(节省内存)。

2. 全局/静态区(Data Segment)

分为两部分:

已初始化全局变量和静态变量区(Initialized Data Segment)

  • 内容:被初始化的全局变量和静态变量。

    1
    2
    
    int a = 10;  // 已初始化全局变量
    static int b = 20;  // 已初始化静态变量
    

未初始化全局变量和静态变量区(BSS Segment)

  • 内容:未显式初始化的全局变量和静态变量(系统自动初始化为 0)。

    1
    2
    
    int a;        // 未初始化全局变量,位于 BSS 段
    static int b; // 未初始化静态变量,位于 BSS 段
    

3. 栈区(Stack Segment)

  • 内容:函数调用过程中的局部变量、函数参数、返回地址、保存的寄存器等。

  • 特点

    • 自动分配和释放,由编译器管理。

    • 遵循先进后出(FILO)的原则。

    • 大小受限,容易造成栈溢出(如递归过深)。

1
2
3
void func() {
    int x = 10;  // 局部变量,位于栈上
}

4. 堆区(Heap Segment)

  • 内容:动态分配的内存(程序运行时用 newmalloc 分配的)。
  • 特点
    • 手动管理(需要使用 freedelete 释放)。
    • 大小较大,但容易造成内存泄漏。
    • 分配和释放效率相对较低,碎片化风险高。
1
2
int* p = new int[100];  // 动态申请的内存,在堆上
delete[] p;             // 释放

5. 常量区(Literal or Constant Segment)

  • 内容:程序中的常量,比如字符串常量、const 修饰的全局变量(有时实现上会被放入代码段)。
1
2
const int c = 100;    // 有些实现中在常量区
char* str = "hello";  // 字符串常量位于常量区

举个例子总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int global_a = 10;      // 已初始化全局变量 → Data Segment
int global_b;           // 未初始化全局变量 → BSS Segment

const int global_c = 30; // 常量 → 可能在常量区

int main() {
    int local_var = 5;    // 局部变量 → 栈区
    static int static_var = 20; // 静态变量 → Data Segment
    int* heap_var = new int(100); // 动态分配 → 堆区

    cout << *heap_var << endl;

    delete heap_var; // 手动释放堆内存
    return 0;
}

各分区的实现细节依赖操作系统和编译器,例如:

  • Windows 和 Linux 的地址布局可能不同;
  • const 局部变量通常还是在栈上;
  • const char* 指向的字符串常量位于常量区,但变量本身在栈上。

更精细的程序内存结构图(以 Linux 为例)

+---------------------------+
| 栈(stack)                 | <-- 高地址     |
| --------------------------- |
| 共享库映射区                |
| --------------------------- |
| 堆(heap)                  |
| --------------------------- |
| .bss(未初始化全局变量)    |
| --------------------------- |
| .data(已初始化的全局变量) |
| --------------------------- |
| .rodata(只读常量)         | ← 常量就在这里 |
| --------------------------- |
| .text(代码段)             | <-- 低地址     |
+---------------------------+

之前的内存结构示意图为何没有常量区:

  • 图中没画常量区,是因为“常量”不是操作系统层面一个独立的内存段;

  • 常量通常在 .rodata(只读数据段),属于数据段一部分;

  • 在教学中,为了方便理解会说“常量区”,但实际它们会被链接到 .rodata 并映射为只读内存页面

本文由作者按照 CC BY 4.0 进行授权