【Linux】15.Linux进程概念(4)

news/2025/1/21 14:29:54/

文章目录

    • 程序地址空间前景回顾
    • C语言空间布局图:
      • 代码1
      • 代码2
      • 代码3
      • 代码4
      • 代码5
      • 代码6
      • 代码7


程序地址空间前景回顾

历史核心问题:

pid_t id = fork();

if(id == 0)

else if(id>0)

为什么一个id可以放两个值呢?之前没有仔细讲。


C语言空间布局图:

749795927c63a65fcb3a88b14d8319a1


代码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;
}

运行结果:

fa863055925237dd0de37d040841b6f9

可以看到,下方的始终要比上方的地址大一点。


代码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;
}

运行结果:

0c2838dd3e6c3b63b6b76796e86b14bc

为什么栈区地址看起来不规律,但实际上栈的增长方向是从高地址到低地址:

  1. 不同类型变量的对齐要求不同
  2. 编译器优化会重排变量位置
  3. 指针变量(str和men)通常会放在一起
  4. 整型变量(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;
}

运行结果:

6f5d7ab1cd43b361f72047501f2b1045

堆区地址上升。


代码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;
}

运行结果:

4483091cf2545a9e5d8925b2bcc545f2

说明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;
}

运行结果:

f46d17003ed59b8528ee71e304f73f75


代码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;
}

运行结果:

a054373b353c63aa0bdbb82444df6f52

我们可以看到子进程改成了200,父进程没有变。

但是这不重要,重要的是他们两个不同的值地址却是一样的。

匪夷所思!但是结论是这样的。

至少我们现在可以认为:如果变量的地址是物理地址,那么是不可能存在上述现象的。所以这个地址绝对不是物理地址。

我们一般叫这个为线性地址,或者虚拟地址。

我们平时写的C/C++,用的指针,指针里面存放的地址全都不是物理地址!


http://www.ppmy.cn/news/1564979.html

相关文章

北京市房屋建筑物轮廓shp数据arcgis高度字段内容下载分析

标题中的“北京市房屋建筑物轮廓shp数据arcgis高度字段”涉及到的是地理信息系统&#xff08;GIS&#xff09;中的数据格式和属性字段。在GIS领域&#xff0c;SHP&#xff08;Shapefile&#xff09;是一种常见的矢量数据格式&#xff0c;用于存储地理空间特征&#xff0c;如点、…

【PCIe 总线及设备入门学习专栏 6.1 -- PCIe MCTP】

文章目录 1 什么是 MCTP?2 MCTP 消息在 PCIe 中的传输特点3 PCIe MCTP 的局限性(1) 出站(Outbound)MCTP 消息分解的限制(2) 入站(Inbound)MCTP 消息组装的限制4 MCTP 消息的实际使用流程发送端处理流程接收端处理流程5 实际使用场景例 1:管理命令传输例 2:监控数据报告例…

实施工程师:面试基础宝典

一.运维工程师和实施工程师的区别&#xff1a;工作内容不同、职能不同、工作形式不同 1.工作内容不同&#xff1a; 运维工程师要对公司硬件和软件进行维护。 硬件包括&#xff1a;机房、机柜、网线光纤、PDU、服 务器、网络设备、安全设备等。 实施工程师包括常用操作系统、应…

怎么使用langchain和ollama自己简单开发搭建一个本地有记忆的大模型?

环境&#xff1a; langchain ollama 问题描述&#xff1a; 怎么使用langchain和ollama开发搭建一个本地有记忆的大模型? 之前有个数字人管家项目&#xff0c;需要新增开发数字人后台大模型的记忆功能&#xff0c;测试了一下市面上的开源项目&#xff0c;没有找到满足自己…

Java开发提效秘籍:巧用Apache Commons IO工具库

一、引言 在 Java 开发的广袤领域中&#xff0c;输入输出&#xff08;I/O&#xff09;操作宛如一座桥梁&#xff0c;连接着程序与外部世界&#xff0c;从文件的读取与写入&#xff0c;到网络数据的传输&#xff0c;I/O 操作无处不在&#xff0c;其重要性不言而喻。然而&#xf…

淘宝关键词页面爬取绘图进行数据分析

对爬虫、逆向感兴趣的同学可以查看文章&#xff0c;一对一小班V教学&#xff1a;https://blog.csdn.net/weixin_35770067/article/details/142514698 关键词页面爬取代码 from DrissionPage import WebPage, ChromiumOptions from DataRecorder import Recorder import time …

数据结构——AVL树的实现

Hello&#xff0c;大家好&#xff0c;这一篇博客我们来讲解一下数据结构中的AVL树这一部分的内容&#xff0c;AVL树属于是数据结构的一部分&#xff0c;顾名思义&#xff0c;AVL树是一棵特殊的搜索二叉树&#xff0c;我们接下来要讲的这篇博客是建立在了解搜索二叉树这个知识点…

Web开发 -前端部分-CSS-2

一 长度单位 代码实现&#xff1a; <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document<…