一、之前所学的程序地址空间

1. 1 空间分布图

在这里插入图片描述

在这里插入图片描述

  1. 经过c语言的学习,相信大家对这张图肯定并不陌生。自下而上,地址递增,分别是正文代码区、字符串常量区、初始化数据区、未初始化数据区、堆区、共享区和栈区。但其实这个图也并不完整,只有在接触了系统之后才能明白完整的图。

  1. 栈区:是用来存储局部变量和函数调用上下文信息的,无论是整型变量、浮点型变量又或是指针变量,都是存储在栈里面的。地址从高向低增长。
  2. 堆区:用来存储动态申请的内存的。地址从低向高增长。
  3. 未初始化数据区即BBS段:是用来存储没有初始化的全局/静态变量,程序启动时会被系统自动初始化为0。
  4. 初始化数据区:用来存储已经初始化的全局/静态变量。
  5. 字符串常量区:用于存储字符串常量。
  6. 正文代码区:存放程序的机器指令(即编译后的二进制可执行代码),是 CPU 执行的操作序列。它包含函数体、控制流(分支、循环)对应的指令等。
  • 未初始化数据区/初始化数据区、字符串常量区和正文代码区的大小的固定的
  • 堆、栈相对而生
1.2 验证
  1. 接下来用下面的一段代码来进行验证上面的空间分布图:

代码语言:javascript

AI代码解释

	1 #include <stdio.h>
    2 #include <unistd.h>
    3 #include <stdlib.h>
    4 int g_unval;
    5 int g_val = 100;
    6 int main(int argc, char *argv[], char *env[])
    7 {
    8  const char *str = "helloworld";
    9  printf("code addr: %p\n", main);
   10  printf("read only string addr: %p\n", str);
   11  printf("init global addr: %p\n", &g_val);
   12  printf("uninit global addr: %p\n", &g_unval);
   13  static int test = 10;
   14  char *heap_mem = (char*)malloc(10);
   15  char *heap_mem1 = (char*)malloc(10);

   16  char *heap_mem2 = (char*)malloc(10);
   17  char *heap_mem3 = (char*)malloc(10);
   18  printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
   19  printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
   20  printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
   21  printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)
   22  printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)                                                                  
   23  printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)
   24  printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)
   25  printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)
   26  printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)
   27 return 0;
   28 }
  1. main是函数名,存放在正文代码区。g_unval 是未初始化的全局变量,存放在未初始化数据区,g_val是已经初始化的全局变量,存放在初始化数据区。str是指针变量,str就是字符串"helloworld"的地址,而&str是指针变量的地址,前者存储在字符串常量区,后者在栈区。test是已经初始化的静态变量,存储在初始化数据区。heap_mem,heap_men1,heap_men2和heap_mem3是指针变量,本身也是地址,它指向堆申请的空间,&heap_mem在栈区,heap_mem在堆区。

在这里插入图片描述

在这里插入图片描述

二、进程/虚拟地址空间

在我们对进程和内存有了一定了解之后,那么这个程序地址空间是指内存吗?若说它是内存,它把空间划分的这样绝对,PCB应该存储在哪里?若说它不是内存,那它究竟是什么东西?根据冯-诺依曼体系,代码和数据就是要放在内存里面的。之所以会有这样的矛盾,这是因为之前学习的东西并不完整,接下来我们来了解进程/虚拟地址空间。

1.1虚拟地址
  1. 进程/虚拟地址空间一定离不开虚拟地址,那进程/虚拟地址是什么呢?我们通过实验认识来认识它:

代码语言:javascript

AI代码解释

//实验代码
	int num = 100;
  6 int main()
  7 {
  8   pid_t id = fork();
  9   if (id == 0)
 10   {
 11     printf("我是一个子进程,pid:%d, ppid:%d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
 12     num = 200;
 13     printf("我是一个子进程,我修改了num的值, num = %d, &num = %p\n", num, &num);
 14   }
 15   else
 16   {
 17     while(1)                                                                                                                             
 18     {
 19       printf("我是一个父进程,pid:%d, ppid:%d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
 20       sleep(1);
 21     }
 22   }
 23   return 0;
 24 }

在这里插入图片描述

在这里插入图片描述

  1. 我设置了一个全局变量num,这个变量子进程和父进程都能够获取到(父子进程代码共享),在子进程修改num这个值之前,父子进程对应num的值都是100,对应num地址都是0x601054。但是在子进程修改了num这个值之后,子进程里显示num值为200,父进程里显示num值为100,子进程没能影响父进程num的值,但它们的地址还是一样的。
  2. 但一个变量能够存储两个不同的值?这显然是不可能的,这说明父子进程输出的并不是同一个变量。也说明这个地址并不是真正的物理地址,而是系统给我们看的一个虚拟地址
  3. 我们所能看到的一切地址都是经过系统处理之后得到的虚拟地址,真正的物理地址我们是看不到的,它被系统给隐藏起来了。
2.1进程/虚拟地址空间

进程/虚拟地址空间不是真正意义上的物理内存,也不是程序地址空间,但对于进程来说它就是内存,完整图如下,相对于程序地址空间它多出来命令行参数环境变量区以及内核空间。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐