目录
2023.12.03.2:00:00讲解具体原因(结合哈希表等等)
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家
一、程序地址空间
1、程序地址空间分布图:
2、通过代码验证上述分布图:
cpp1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int g_uval; 6 int g_val = 100; 7 8 int main() 9 { 10 printf("代码段地址:%p\n",main); 11 printf("初始化变量的地址:%p\n",&g_val); 12 printf("未初始化变量的地址:%p\n",&g_uval); 13 char* heap = (char*)malloc(20); 14 printf("heap addr:%p\n",heap); 15 printf("栈上的地址:%p\n",&heap); 16 17 return 0; 18 } ~
可以看到地址的大小走向与程序地址空间分布图一致。
命令行参数表和环境变量表的地址比较:
cpp1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 5 int g_uval; 6 int g_val = 100; 7 8 int main(int argc,char* argv[],char*env[]) 9 { 10 for(int i = 0;argv[i];i++) 11 { 12 printf("argv[%d]:%p\n",i,&argv[i]); 13 } 14 for(int i =0;env[i];i++) 15 { 16 printf("env[%d]:%p\n",i,env+i); 17 } 18 19 return 0; 20 }
注意:先有命令行参数表,后有环境变量表(可搜索寻求原因)
可根据上面对比看出,不论是表还是表中的内容,地址都在栈上面;
3、观察一个奇怪的现象(虚拟内存)
cpp1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 #include <sys/types.h> 5 6 int g_uval; 7 int g_val = 100; 8 9 int main(int argc,char* argv[],char*env[]) 10 { 11 //观察一个奇怪的现象: 12 pid_t id = fork(); 13 if(id == 0) 14 { 15 //子进程执行 16 int cnt = 0; 17 while(1) 18 { 19 printf("child process:\npid:%d\nppid%d\ng_val:%d\n&g_val:%p\n",getpid(),getppid(),g_val,&g_val); 20 sleep(2); 21 cnt++; 22 if(cnt == 5) 23 { 24 g_val = 200; 25 printf("child change g_val:100->200\n"); 26 } 27 28 29 } 30 } 31 else 32 { 33 //父进程执行 34 while(1) 35 { 36 printf("father process:\npid:%d\nppid:%d\ng_val:%d\n&g_val:%p\n",getpid(),getppid(),g_val,&g_val); 37 sleep(2); 38 } 39 } 40 41 return 0; 42 }
刚开始父子进程共享全局变量g_val,过了一会子进程将g_val由100修改为了200,发现子进程中g_val变为了200,但父进程中的g_val还是100,这不难理解,因为进程之间具有独立性,子进程修改变量不会影响父进程。
但奇怪的是,为什么父子进程中的g_val值不一样,但地址却是一样的。
原因在于,我们所看到的这些地址不是物理地址,而是虚拟地址(也叫线性地址)。说明我们平时写代码使用&取到的地址都不是物理地址,而是虚拟地址,同时也说明了进程地址空间也不是物理内存。
2023.12.03.2:00:00讲解具体原因(结合哈希表等等)
二、什么是进程地址空间?
每一个进程都会存在一个进程地址空间(假设范围为[0,4GB]);
操作系统也需要对进程地址空间进行管理,管理方法:先描述,再组织;
进程地址空间其实也是数据结构,具体到进程中,就是特定的数据结构对象;
1、进程与进程地址空间的关系
其实就是数据结果之间的关系:
我们知道进程PCB里面记录了关于进程的大部分信息,所以PCB里面也有一个进程地址空间结构体指针。进程地址空间也是一个结构体,里面有个自身类型指针,将各个进程地址空间以链接到某种数据结构中(比如链表),然后PCB中的进程地址空间结构体指针指向这个数据结构的起始地址,就这样管理起来了。
2、页表
区域划分本质就是说区域内的各个地址都可以使用。我们的地址空间不具备对我们的代码合数据的保存能力,是在物理内存中存放的。我们会通过进程地址的虚拟地址转化到物理内存中,这是通过页表实现的转化。
页表是一张哈希表,用于建立虚拟地址和物理地址的映射,从而通过虚拟地址找到对应的物理地址
3、为什么要有地址空间+页表
(1)可以将物理内存从无序变成有序,让进程以统一视角去看待内存。
(2)将进程管理和内存管理解耦合。
(3)也是内存安全的一个手段:比如遇到野指针这些问题,进程会直接挂掉,但不影响其他进程
4、问题:
(1)malloc和new申请内存时,申请的内存会直接立马使用?
当然是不一定的,什么时候用取决于用户,此时会在进程地址空间上面申请,当用户使用这份空间的时候才会通过页表去和物理内存建立映射。
(2)申请内存本质是在进程地址空间上申请的。
(3)好处:可以充分保证内存的使用率,不会空转;还会提升new和malloc的速度。