一、引入:一个诡异的例子
我们在程序中 fork
创建一个子进程:
让子进程死循环打印自己的 pid、ppid
,还有全局变量 gval
及其地址,同时子进程每轮 gval++
让父进程死循环打印自己的 pid、ppid
,还有全局变量 gval
及其地址
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int gval = 100;
int main() {
printf("我是一个进程, pid :%d, ppid :%d\n", getpid(), getppid());
pid_t id = fork();
if (id == 0) {
// child
while(1) {
printf("我是子进程, pid :%d, ppid :%d, gval:%d, &gval:%p\n", getpid(), getppid(), gval, &gval);
gval++;
sleep(1);
}
}
else {
// parent
while(1) {
printf("我是父进程, pid :%d, ppid :%d, gval:%d, &gval:%p\n", getpid(), getppid(), gval, &gval);
sleep(1);
}
}
}
运行结果如下:
子进程确实按程序每轮 gval++
,而父进程的 gval
一直保持 100,这些都正常,父子进程都按照自己的代码逻辑运行。
但是,你有没有发现,父子进程的 gval
的地址是一样的!!!!!
为什么?因为进程具有独立性,父子进程中的 gval
都以自己的代码逻辑变化,他们指向的却还是 “同一个 gval
” ?,那如果指向同一个 gval
,那为什么父子进程的 gval
不同值??
这就涉及到虚拟地址空间了,这里暂时可以输出一个结论:此处父子进程打印出来的 gval
的地址是虚拟地址!,绝非以前学习所理解的 物理内存地址。
二、认识进程地址空间
下面这张图展示的就是进程地址空间:其中划分了几块不同的分区。
在我们学习C语言时,很多网课或博客都会以下面这张图讲解C语言的内存空间分布,讲解不同类型数据的存储位置。
但是,在一些C语言的教材中却看不到这张图,这是因为下面这张图不是程序地址空间,而是进程地址空间,是系统范畴的,而不属于语言范畴的。
进一步认识进程地址空间:大富翁的例子
某个国家有个大富翁,该大富翁的钱被以一种顺序排序着,就是所有的钱都有对应编号,想要用钱时直接在机器上输入某某编号就能取出对应位置编号的钱。这个大富翁有很多子女,大富翁允许这些子女取自己的钱使用,当然也是通过输入编号取出对应位置一定数额的钱。然而有一天,两个儿子打起来了,原因是大儿子想要取出编号为 1 的 100 元,而二儿子在第二台机器上也想要取出编号为 1 的 100 元,这就造成了冲突,编号为 1 的 100 元只有一张,不能同时从两个机器中取出,因此他们就产生了冲突。
而在平行宇宙中,存在另一个十亿资产大富翁,他在外头有很多私生子,大富翁给他们每个人画了一个大饼:”你爹我有十个亿,你可以使用我的十个亿,但使用前需要向我申请“。可是!各个私生子之间互相不知道对方的存在,都以为自己是大富翁的唯一后代,因此都认为自己”已经拥有了“自己父亲的全部十亿资产!!当某个私生子向父亲要钱时,大富翁就在自己余额中取钱给这个私生子。但是私生子们不知道,其实父亲的资产只剩下一亿了,另外9亿是被其他私生子霍霍光的,可是由于各个私生子都以为自己拥有的是父亲的十亿资产……
因此,在私生子的认知里,自己拥有父亲所有资产,实际上父亲的资产已经没有这么多了
在这个例子中
第一个大富翁的资产就是实际的物理内存大小,儿女就是