在以往的学习中我们经常接触地址,电脑像一个小房间,它的空间是有限不可重叠的,但是可以覆盖。想象一下如果我们要放很多东西进去,如果没有合理的安排,所有东西乱放,那么我们需要寻找某一个东西的时候需要把房间找一个遍,也就是遍历,但是如果我们把房间的每一个地方都打上标签或者起个名字,然后把每个东西放在哪里记录下来放在一张纸上,我们就可以通过纸来快速定位物品。纸上写的物品信息,就是我们俗称的地址。但是我们有没有想过,我们接触的Linux地址有没有可能不是真正的地址,这就是我接下来要讲的东西了。
一,小代码测试
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
g_val=100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}else{ //parent
sleep(3);
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
当运行这段程序之后,我们会发现一个神奇的现象,父子进程g_val的地址居然一样,这是什么情况,一个地址能存储两个值?这不是违背了常识吗,难道是这个空间被分割了,一半储存父进程的值,一半存储子进程的值?
二,谜底揭晓
其实我们看到的并不是系统底层变量真正的地址,而是经过一定处理过后的地址,这个处理手段叫页表,页表里面会将我们所看到的地址转化为实际在电脑中的地址,看似两个g_val的地址一样,但实际经过页表的转化会变成不同的地址。从我查过的资料可得,页表里面首先会记录这个变量或者代码的实际存储范围,可能是由一段或者多段空间组成(指的是地址可能不连续),这段地址与我们看到的地址是对应关系,我们先看一张图来建立大概印象。
那这样子的好处是什么呢,不是多此一举吗?当然不是,首先我们要明白在系统中有时候能分配给我们的空间并不是连续的,如果我们程序员直接处理这些不连续的地址无疑会增加难度,也带来了很多不便,对于新手,地址的理解只会更加混乱,那怎么办呢?我们可不可以封装一下呢,当然可以,封装之后我们无法直接接触底层地址,大大降低了我们的学习难度,同时又能很好的利用碎片空间,也可以保护底层不被用户瞎篡改。