操作系统?进程地址空间!!!
一、进程地址空间的概念
在我们之前的理解中,我们写出的C、C++代码通过取地址符‘&’对某一个变量取出的地址就是它真实的地址,但真的是这样吗?答案是否定的,事实上,在下面一段代码以及它的执行结果我们就可以看得出来:
#include <iostream>
#include <unistd.h>
using namespace std;
int x = 0;
int main(){pid_t id = fork();if(id == 0){x = 1;printf("子进程打印x的地址:%p\n",&x);}else if(id > 0){if(x == 0){printf("父子进程有独立的内存空间\n");}else{printf("父子进程共享内存空间\n");}printf("父进程打印x的地址:%p\n",&x);}return 0;
}
运行结果:

从上图就可以看出,当子进程改写全局变量x的时候,父进程中的x并没有发生变化,所以对于父子进程来讲,它们视角中的x应该不是同一个x,但是它们打印出的x的地址却是相同的,所以‘&’取地址符取出的并不是变量在物理内存中的真实地址,而是一个虚拟的地址!
- 进程地址空间的定义:进程地址空间是一块连续的虚拟地址空间(在32为机器下是0~4GB,低3GB由用户分配,高1GB为内核区),由操作系统统一分配与管理
- 进程地址空间的本质:是“物理内存的抽象”,目的是让每一个进程误以为自己独占整个内存空间
- 虚拟地址通过操作系统的映射机制转换为物理内存才能访问实际内存
二、进程地址空间的划分
操作系统会将进程地址空间按照功能划分为多个区域,从低到高通常如下:
- 用户区(低3GB)
- 代码段(.text):存储进程的可执行指令,只读不写,避免指令被篡改。
- 数据段(.data):存储已初始化的全局变量和静态变量,读写权限均可。
- BSS 段(.bss):存储未初始化的全局变量和静态变量,程序启动时由操作系统初始化为 0。
- 堆(Heap):动态内存分配区域,由进程主动申请(如 malloc、new)和释放(如 free、delete),地址从低向高增长。
- 共享库 / 动态链接库区域:加载进程依赖的系统库(如 C 标准库)或第三方库,实现代码复用。
- 栈(Stack):存储函数调用栈帧(参数、局部变量、返回地址),地址从高向低增长,自动分配和释放。
- 命令行参数与环境变量区:存储进程启动时的命令行参数(argv)和环境变量(envp),位于栈的最顶端
- 内核区
- 存储操作系统内核代码、内核数据结构、硬件驱动等核心资源。
- 进程无法直接访问该区域,需通过系统调用(如 syscall)切换到内核态才能间接使用。
三、地址映射机制
- 进程使用的虚拟地址,必须通过 “虚拟地址→物理地址” 的映射才能访问实际物理内存。
- 页表存储着虚拟内存与物理内存的映射关系,进程地址空间首元素指针储存在PCB中,同时页表的指针也作为上下文的形式放在PCB中
- 映射由页表和内存管理单元(MMU) 协同完成:页表记录虚拟页与物理页的对应关系,MMU 负责硬件层面的地址转换。
- 若虚拟地址未映射物理内存(如首次访问(写时拷贝)、内存不足),会触发缺页中断,由操作系统分配物理内存并更新页表。
四、核心意义
- 进程隔离:不同的进程的虚拟内存空间独立,一个进程的内存错误不会影响其它进程
- 简化内存管理:进程不需要关心物理内存实际分布如何,只需按虚拟地址编程
- 解耦:通过将进程管理和内存管理分开,实现解耦,低耦合度是我们更追求的
五、结语
以上就是有关内存地址空间的全部知识了,欢迎大家和我一起交流、学习、进步!!!
