当前位置: 首页 > article >正文

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

文章目录

    • 程序地址空间前景回顾
    • 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.kler.cn/a/509955.html

相关文章:

  • 1.6 从 GPT-1 到 GPT-3.5:一路的风云变幻
  • 多种vue前端框架介绍
  • lvm快照备份技术详细知识点
  • 【实践】操作系统智能助手OS Copilot新功能测评
  • Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
  • Ubuntu VPS 上 Docker 部署 Nginx 服务器详细教程
  • linux 安装jdk1.8
  • 【脑机接口数据处理】bdf文件转化mat文件
  • AI Prompt 设计指南:从基础构建到高质量生成的全面解析
  • h5使用video播放时关掉vant弹窗视频声音还在后台播放
  • Centos7将/dev/mapper/centos-home磁盘空间转移到/dev/mapper/centos-root
  • 分布式CAP理论介绍
  • Dart语言
  • 计算机视觉语义分割——U-Net(Convolutional Networks for Biomedical Image Segmentation)
  • 【视觉惯性SLAM:十六、 ORB-SLAM3 中的多地图系统】
  • 深入探索Go语言中的临时对象池:sync.Pool
  • Vue2.0的安装
  • K210视觉识别模块
  • 向harbor中上传镜像(向harbor上传image)
  • 模块化架构与微服务架构,哪种更适合桌面软件开发?
  • 【Unity】使用UniRx来快速完成Unity中的信号层开发工作。
  • Navicat Premium 数据可视化
  • 基于SSM汽车美容管家【提供源码+答辩PPT+文档+项目部署】(高质量源码,可定制,提供文档,免费部署到本地)
  • 【JSqlParser】Java使用JSqlParser解析SQL语句总结
  • 事件委托,其他事件,电梯导航,固定导航
  • Linux 音视频入门到实战专栏(视频篇)视频编解码 MPP