文章目录
- 程序地址空间前景回顾
- C语言空间布局图:
- 代码1
- 代码2
- 代码3
- 代码4
- 代码5
- 代码6
- 代码7
程序地址空间前景回顾
历史核心问题:
pid_t id = fork();
if(id == 0)
else if(id>0)
为什么一个
id
可以放两个值呢?之前没有仔细讲。
C语言空间布局图:
代码1
来看一段代码:
#include <stdio.h>
#include <stdlib.h>
int main(){
char *str = "hello linux";
*str = 'H';
printf("xxx=%s\n",getenv("xxx")); // 获取环境变量xxx的值
return 0;
}
这段代码编译会报错。
因为"hello linux"
储存在字符常量区,具有只读属性, *str = 'H';
这个代码表面上我们是想要把第一个h
换成H
,但是因为只有只读属性,无法更改。
代码2
接下来写一段代码:
#include <stdio.h>
#include <stdlib.h>
int g_val_1;//未初始化全局变量
int g_val_2 = 100;//已初始化全局变量
int main(){
printf("code addr:%p\n",main); // 打印main函数的地址,位于代码段
const char* str="hello linux";
printf("read only string addr:%p\n",str); // 打印字符串常量的地址,位于只读数据段
printf("init global value affr:%p\n",&g_val_2); // 打印已初始化的全局变量地址,位于数据段
printf("uninit global value affr:%p\n",&g_val_1); // 打印未初始化的全局变量地址,位于BSS段
char *men = (char*)malloc(100);
printf("head addr:%p\n",men); // 打印堆区地址
printf("stack addr:%p\n",&str); // &str是指针变量本身的地址,而不是它指向的字符串的地址
return 0;
}
运行结果:
可以看到,下方的始终要比上方的地址大一点。
代码3
我们还可以把代码再改一下:
#include <stdio.h>
#include <stdlib.h>
int g_val_1;//未初始化全局变量
int g_val_2 = 100;//已初始化全局变量
int main(){
printf("code addr:%p\n",main); // 打印main函数的地址,位于代码段
const char* str="hello linux";
printf("read only string addr:%p\n",str); // 打印字符串常量的地址,位于只读数据段
printf("init global value affr:%p\n",&g_val_2); // 打印已初始化的全局变量地址,位于数据段
printf("uninit global value affr:%p\n",&g_val_1); // 打印未初始化的全局变量地址,位于BSS段
char *men = (char*)malloc(100);
printf("head addr:%p\n",men); // 打印堆区地址
printf("stack addr:%p\n",&str); // &str是指针变量本身的地址,而不是它指向的字符串的地址
printf("stack addr:%p\n",&men); // 打印men指针变量本身的地址,位于栈区
int a; // 定义3个局部变量,它们都位于栈区
int b; // 栈区地址从高到低分配
int c;
printf("stack addr:%p\n",&a); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&b); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&c); // 打印局部变量的地址,位于栈区
return 0;
}
运行结果:
为什么栈区地址看起来不规律,但实际上栈的增长方向是从高地址到低地址:
- 不同类型变量的对齐要求不同
- 编译器优化会重排变量位置
- 指针变量(str和men)通常会放在一起
- 整型变量(a,b,c)通常会放在一起
代码4
我们还可以把代码再改一下:
#include <stdio.h>
#include <stdlib.h>
int g_val_1;//未初始化全局变量
int g_val_2 = 100;//已初始化全局变量
int main(){
printf("code addr:%p\n",main); // 打印main函数的地址,位于代码段
const char* str="hello linux";
printf("read only string addr:%p\n",str); // 打印字符串常量的地址,位于只读数据段
printf("init global value affr:%p\n",&g_val_2); // 打印已初始化的全局变量地址,位于数据段
printf("uninit global value affr:%p\n",&g_val_1); // 打印未初始化的全局变量地址,位于BSS段
char *men = (char*)malloc(100);
char *men1 = (char*)malloc(100);
char *men2 = (char*)malloc(100);
printf("head addr:%p\n",men); // 打印堆区地址
printf("head addr:%p\n",men1); // 打印堆区地址
printf("head addr:%p\n",men2); // 打印堆区地址
printf("stack addr:%p\n",&str); // &str是指针变量本身的地址,而不是它指向的字符串的地址
printf("stack addr:%p\n",&men); // 打印men指针变量本身的地址,位于栈区
int a; // 定义3个局部变量,它们都位于栈区
int b; // 栈区地址从高到低分配
int c;
printf("stack addr:%p\n",&a); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&b); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&c); // 打印局部变量的地址,位于栈区
return 0;
}
运行结果:
堆区地址上升。
代码5
验证一个语法问题:
为什么用static
修饰的变量不会被释放呢?
我们把代码改一下:
#include <stdio.h>
#include <stdlib.h>
int g_val_1;//未初始化全局变量
int g_val_2 = 100;//已初始化全局变量
int main(){
printf("code addr:%p\n",main); // 打印main函数的地址,位于代码段
const char* str="hello linux";
printf("read only string addr:%p\n",str); // 打印字符串常量的地址,位于只读数据段
printf("init global value affr:%p\n",&g_val_2); // 打印已初始化的全局变量地址,位于数据段
printf("uninit global value affr:%p\n",&g_val_1); // 打印未初始化的全局变量地址,位于BSS段
char *men = (char*)malloc(100);
char *men1 = (char*)malloc(100);
char *men2 = (char*)malloc(100);
printf("head addr:%p\n",men); // 打印堆区地址
printf("head addr:%p\n",men1); // 打印堆区地址
printf("head addr:%p\n",men2); // 打印堆区地址
printf("stack addr:%p\n",&str); // &str是指针变量本身的地址,而不是它指向的字符串的地址
printf("stack addr:%p\n",&men); // 打印men指针变量本身的地址,位于栈区
static int a; // 定义3个局部变量,它们都位于栈区
int b; // 栈区地址从高到低分配
int c;
printf("a = stack addr:%p\n",&a); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&b); // 打印局部变量的地址,位于栈区
printf("stack addr:%p\n",&c); // 打印局部变量的地址,位于栈区
return 0;
}
运行结果:
说明static
修饰的局部变量,编译的时候就已经被编译到全局数据区了。
代码6
我们改一下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0){
//子进程
while(1){
printf("i am child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
}
}
else{
//父进程
while(1){
printf("i am parent, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
}
}
return 0;
}
运行结果:
代码7
然后改一下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0){
int cnt = 5;
//子进程
while(1){
printf("i am child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
if(cnt) cnt--;
else {
g_val = 200;
printf("子进程change g-val : 100->200\n");
cnt--;
}
}
}
else{
//父进程
while(1){
printf("i am parent, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
}
}
return 0;
}
运行结果:
我们可以看到子进程改成了200
,父进程没有变。
但是这不重要,重要的是他们两个不同的值
,地址却是一样
的。
匪夷所思!但是结论是这样的。
至少我们现在可以认为:如果变量的地址是物理地址,那么是不可能存在上述现象的。所以这个地址绝对不是物理地址。
我们一般叫这个为线性地址,或者虚拟地址。
我们平时写的C/C++
,用的指针,指针里面存放的地址全都不是物理地址!