在程序运行时,内存空间通常会被划分为多个功能不同的区域,不同操作系统和编程语言的划分方式可能略有差异,但总体结构相似。
一、栈区(Stack)
- 分配方式:由操作系统自动分配和释放,存放函数的参数值、局部变量等。
- 特点:
- 后进先出(LIFO):遵循栈的数据结构规则,最后进入的变量最先被释放。
- 大小有限:栈的大小通常由系统预先设定(如 Linux 中默认栈大小约为 8MB),若递归深度过深或局部变量过大,可能导致栈溢出(Stack Overflow)。
- 速度快:直接由 CPU 寄存器操控,访问速度极快。
- 示例:函数调用时,形参和函数内定义的局部变量(如 int a = 10;)存储在栈区。
二、堆区(Heap)
- 分配方式:由程序员手动申请(如 C 语言的 malloc/free,C++ 的 new/delete),操作系统动态分配内存。
- 特点:
- 动态灵活:内存分配和释放由程序控制,适用于运行时大小不确定的数据(如动态数组、链表)。
- 可能产生碎片:频繁的申请和释放可能导致内存碎片,影响分配效率。
- 大小限制:理论上受限于系统虚拟内存,但实际使用中需注意内存泄漏问题(忘记释放已申请的内存)。
- 示例:int* arr = (int*)malloc(10*sizeof(int)); 申请的内存位于堆区。
三、全局数据区(静态区,Static Area)
- 分配方式:程序编译时分配,存放全局变量和静态变量(static 修饰的变量)。
- 子分区:
- 初始化全局区(初始化静态区):存储已初始化的全局变量和静态变量。
- 未初始化全局区(BSS 段):存储未初始化的全局变量和静态变量,程序运行前由系统自动初始化为 0 或空指针。
- 特点:
- 生命周期长:从程序启动到结束始终存在。
- 共享性:全局变量可被程序中的所有函数访问。
- 示例:
int global_var = 10; // 初始化全局区 static int static_var = 20; // 初始化静态区 int uninitialized_global; // BSS 段
四、文字常量区(常量区,Constant Area)
- 分配方式:程序编译时分配,存放字符串常量和基本类型常量(如 int a = 10; 中的 10 可能存放在此,但具体取决于编译器优化)。
- 特点:
- 只读性:内容不可修改,否则会导致程序崩溃(如尝试修改字符串常量 "hello" 会触发运行时错误)。
- 共享机制:相同的常量可能只存储一份,节省内存(如多个指针指向同一字符串常量)。
- 示例:
char* str = "hello world"; // "hello world" 存放在常量区,str 指向该地址
五、代码区(Text Segment)
- 分配方式:程序编译时分配,存放编译后的可执行代码(机器指令)和只读数据(如常量表达式)。
- 特点:
- 只读性:代码在运行时不可修改,确保程序稳定性。
- 共享性:多个进程可共享同一程序的代码区(如 Linux 中的 fork 机制)。
- 示例:函数体中的代码逻辑(如 if-else、循环语句)编译后存放在代码区。
六、其他特殊区域(可选)
- 内核空间(Kernel Space):
- 操作系统内核运行的区域,用户程序无法直接访问,用于管理硬件资源、进程调度等。
- 内存映射区(Memory-Mapped File):
- 通过 mmap 等系统调用将文件或设备映射到内存,用于高效的 I/O 操作(如读取大文件)。
- 线程私有存储(Thread Local Storage, TLS):
- 多线程程序中,为每个线程单独分配的存储区域,用于存放线程局部变量。
内存分区示意图
高地址
├───────────────┤
│ 内核空间 │
├───────────────┤
│ 堆区 │ ←─ 由低地址向高地址增长
├───────────────┤
│ 内存映射区 │
├───────────────┤
│ 栈区 │ ←─ 由高地址向低地址增长
├───────────────┤
│ 代码区 │
│ 文字常量区 │
│ 全局数据区 │
└───────────────┘
低地址
总结
- 栈区:自动管理,存放局部变量,速度快但大小受限。
- 堆区:手动管理,动态分配内存,灵活但需避免泄漏和碎片。
- 全局数据区:存储全局变量和静态变量,生命周期与程序一致。
- 常量区:存放只读常量,防止被意外修改。
- 代码区:存储可执行代码,具备只读和共享特性。