在程序运行时,操作系统会将内存划分为不同区域(分区),每个区域有特定的用途、生命周期和访问规则。
一、内存分区的整体结构
从低地址到高地址,内存通常分为以下几个区域:
代码区 → 常量区 → 全局/静态存储区 → 堆区 → 栈区 → 内核区
(注:不同系统的地址分布可能略有差异,但逻辑分区一致)
二、各分区的详细说明
1. 代码区
-
用途:存储程序的机器指令(二进制代码),即编译后的可执行代码。
-
特点:
- 只读(防止程序意外修改指令),部分系统允许执行权限。
- 编译时确定大小,程序运行期间不改变。
- 共享性:多个进程运行同一程序时,可共享同一份代码区(节省内存)。
-
示例:main函数、自定义函数的二进制指令都存放在这里。
2. 常量区
- 用途:存储常量数据,包括字符串常量、const 修饰的全局常量等。
- 特点:
- 只读(修改会导致程序崩溃,如char* str = "hello"; str[0] = 'H'是错误的)。
- 生命周期与程序一致(从程序启动到结束)。
- 编译时确定大小,存储在可执行文件中。
const int MAX = 100; // MAX存于常量区
char* str = "hello world"; // "hello world"字符串常量存于常量区,str是指针变量
3. 全局 / 静态存储区
- 用途:存储全局变量和静态变量(包括static修饰的局部变量和全局变量)。
- 细分:
- 已初始化区(Data Segment):存储已初始化的全局 / 静态变量(如int a = 10; static int b = 20;)。
- 未初始化区(BSS Segment):存储未初始化的全局 / 静态变量(如int c; static int d;),程序启动时会自动初始化为 0。
- 特点:
- 生命周期与程序一致(随程序启动而分配,结束而释放)。
- 编译时确定大小,存储在可执行文件中(BSS 区仅记录变量名和大小,不占用文件空间)。
int g_var; // 未初始化全局变量 → BSS区
static int s_var = 5; // 已初始化静态变量 → Data区
void func()
{static int ls_var; // 未初始化局部静态变量 → BSS区(生命周期同程序)
}
4. 堆区
- 用途:存储程序运行时动态分配的内存(如malloc/new申请的内存)。
- 特点:
- 动态分配:大小在程序运行时确定,需手动申请(malloc/new)和释放(free/delete)。
- 地址增长方向:从低地址向高地址(与栈区相反)。
- 无序性:多次分配和释放后会产生内存碎片。
- 生命周期:由程序员控制,若未释放会导致内存泄漏(程序结束后由操作系统回收)。
int* p1 = (int*)malloc(4); // 堆区分配4字节
int* p2 = new int[10]; // 堆区分配40字节(10个int)
free(p1); // 释放p1指向的堆内存
delete[] p2; // 释放p2指向的堆内存
5. 栈区
- 用途:存储函数调用时的上下文信息,包括局部变量、函数参数、返回地址等。
- 特点:
- 自动管理:由编译器自动分配和释放(函数调用时分配,函数返回时释放)。
- 地址增长方向:从高地址向低地址(与堆区相反)。
- 大小固定:默认栈大小有限(通常几 MB),超出会导致栈溢出(Stack Overflow)。
- 顺序性:遵循 “先进后出(FILO)” 原则,类似数据结构中的栈。
void func(int a)
{int b = 10; // a(参数)和b(局部变量)都存储在栈区
}
int main()
{func(5); // 调用func时,栈区为a、b分配空间;函数返回后自动释放return 0;
}
6. 内核区
- 用途:操作系统内核代码和数据的专用区域,用户程序无法直接访问(受硬件保护)。
- 特点:
- 高地址区域,大小由操作系统决定(如 32 位系统通常划分 2GB 给内核区)。
- 用于进程调度、内存管理、设备驱动等核心操作。
三、各分区的核心区别(对比表)
四、常见内存问题与分区的关系
- 栈溢出:函数递归过深或局部变量过大(如int arr[100000];),超出栈区大小。
- 内存泄漏:堆区内存未手动释放(如new后未delete),导致程序运行中可用内存逐渐减少。
- 野指针:堆区内存释放后,指针未置空,后续访问可能操作已回收的内存(导致程序崩溃或数据错乱)。
- 常量修改错误:试图修改常量区数据(如char* str = "abc"; str[0] = 'x'),会触发运行时错误。