【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++,用的指针,指针里面存放的地址全都不是物理地址!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955858.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【无法下载github文件】虚拟机下ubuntu无法拉取github文件

修改hosts来进行解决。 步骤一&#xff1a;打开hosts文件 sudo vim /etc/hosts步骤二&#xff1a;查询 github.com的ip地址 https://sites.ipaddress.com/github.com/#ipinfo将github.com的ip地址添加到hosts文件末尾&#xff0c;如下所示。 140.82.114.3 github.com步骤三…

Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin

Android BitmapShader实现狙击瞄具十字交叉线准星&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.…

Android系统开发(十):标准协议和通讯的桥梁:探索蓝牙、NFC、WLAN 的工作原理

引言&#xff1a; 现代社会已经是信息互联的世界&#xff0c;各种设备之间的互联互通已经成为了生活的一部分。而在这个过程中&#xff0c;Android 设备与其他硬件之间的通信扮演着至关重要的角色。从蓝牙耳机到 WiFi 路由器&#xff0c;甚至与电话功能的互动&#xff0c;所有…

node中文名的js文件有问题

新版Node无法运行含有中文名的JS文件&#xff0c;具体表现在无报错无反应。如下图&#xff1a; 源码如下&#xff1a; 改成英文的JS文件&#xff0c;则正常&#xff0c;如下图&#xff1a;

BERT与CNN结合实现糖尿病相关医学问题多分类模型

完整源码项目包获取→点击文章末尾名片&#xff01; 使用HuggingFace开发的Transformers库&#xff0c;使用BERT模型实现中文文本分类&#xff08;二分类或多分类&#xff09; 首先直接利用transformer.models.bert.BertForSequenceClassification()实现文本分类 然后手动实现B…

openharmony应用开发快速入门

开发准备 本文档适用于OpenHarmony应用开发的初学者。通过构建一个简单的具有页面跳转/返回功能的应用&#xff08;如下图所示&#xff09;&#xff0c;快速了解工程目录的主要文件&#xff0c;熟悉OpenHarmony应用开发流程。 在开始之前&#xff0c;您需要了解有关OpenHarmon…

RabbitMQ---TTL与死信

&#xff08;一&#xff09;TTL 1.TTL概念 TTL又叫过期时间 RabbitMQ可以对队列和消息设置TTL&#xff0c;当消息到达过期时间还没有被消费时就会自动删除 注&#xff1a;这里我们说的对队列设置TTL,是对队列上的消息设置TTL并不是对队列本身&#xff0c;不是说队列过期时间…

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…

多语言插件i18n Ally的使用

先展示一下效果 1.第一步首先在vscode下载插件 2.第二步在 setting.json 里面配置 要区分文件是js&#xff0c;ts或json结尾 以zh.ts和en.ts结尾的用这个 { "i18n-ally.localesPaths": ["src/locales"],"i18n-ally.keystyle": "nested"…

蓝桥杯备考:堆和priority queue(优先级队列)

堆的定义 heap堆是一种特殊的完全二叉树&#xff0c;对于树中的每个结点&#xff0c;如果该结点的权值大于等于孩子结点的权值&#xff0c;就称它为大根堆&#xff0c;小于等于就叫小根堆&#xff0c;如果是大根堆&#xff0c;每个子树也是符合大根堆的特征的&#xff0c;如果是…

【人工智能】:搭建本地AI服务——Ollama、LobeChat和Go语言的全方位实践指南

前言 随着自然语言处理&#xff08;NLP&#xff09;技术的快速发展&#xff0c;越来越多的企业和个人开发者寻求在本地环境中运行大型语言模型&#xff08;LLM&#xff09;&#xff0c;以确保数据隐私和提高响应速度。Ollama 作为一个强大的本地运行框架&#xff0c;支持多种先…

Java锁 从乐观锁和悲观锁开始讲 面试复盘

目录 面试复盘 Java 中的锁 大全 悲观锁 专业解释 自我理解 乐观锁 专业解释 自我理解 悲观锁的调用 乐观锁的调用 synchronized和 ReentrantLock的区别 相同点 区别 详细对比 总结 面试复盘 Java 中的锁 大全 悲观锁 专业解释 适合写操作多的场景 先加锁可以…

OpenVela——专为AIoT领域打造的开源操作系统

目录 一、系统背景与开源 1.1. 起源 1.2. 开源 二、系统特点 2.1. 轻量化 2.2. 标准兼容性 2.3. 安全性 2.4. 高度可扩展性 三、技术支持与功能 3.1. 架构支持 3.2. 异构计算支持 3.3. 全面的连接套件 3.4. 开发者工具 四、应用场景与优势 4.1. 应用场景 4.2. …

使用 Java 实现基于 DFA 算法的敏感词检测

使用 Java 实现基于 DFA 算法的敏感词检测 1. 引言 敏感词检测在内容审核、信息过滤等领域有着广泛的应用。本文将介绍如何使用 DFA&#xff08;Deterministic Finite Automaton&#xff0c;确定有限状态自动机&#xff09; 算法&#xff0c;在 Java 中实现高效的敏感词检测。…

单片机存储器和C程序编译过程

1、 单片机存储器 只读存储器不是并列关系&#xff0c;是从ROM发展到FLASH的过程 RAM ROM 随机存储器 只读存储器 CPU直接存储和访问 只读可访问不可写 临时存数据&#xff0c;存的是CPU正在使用的数据 永久存数据&#xff0c;存的是操作系统启动程序或指令 断电易失 …

UDP报文格式

UDP是传输层的一个重要协议&#xff0c;他的特性有面向数据报、无连接、不可靠传输、全双工。 下面是UDP报文格式&#xff1a; 1&#xff0c;报头 UDP的报头长度位8个字节&#xff0c;包含源端口、目的端口、长度和校验和&#xff0c;其中每个属性均为两个字节。报头格式为二…

2024年我的技术成长之路

2024年我的技术成长之路 大家好&#xff0c;我是小寒。又到年底了&#xff0c;一年过得真快啊&#xff01;趁着这次活动的机会&#xff0c;和大家聊聊我这一年在技术上的收获和踩过的坑。 说实话&#xff0c;今年工作特别忙&#xff0c;写博客的时间比去年少了不少。不过还是…

HTML5+Canvas实现的鼠标跟随自定义发光线条源码

源码介绍 HTML5Canvas实现的鼠标跟随自定义发光线条特效源码非常炫酷&#xff0c;在黑色的背景中&#xff0c;鼠标滑过即产生彩色变换的发光线条效果&#xff0c;且线条周围散发出火花飞射四溅的粒子光点特效。 效果预览 源码如下 <!DOCTYPE html PUBLIC "-//W3C//D…

爬虫第二篇

太聪明了怎么办&#xff1f;那就&#xff0c;给脑子灌点水&#xff01;&#xff01; 本篇文章我们来简单讲一下如何爬取mv,也就是歌曲视频&#xff0c;那么我们进入正题。 由于上次拿网易云开了刀&#xff0c;那么这次我们拿酷狗开刀。 还是进入上次讲过的页面 注意&#xff…

C#表达式和运算符

本文我们将学习C#的两个重要知识点&#xff1a;表达式和运算符。本章内容会理论性稍微强些&#xff0c;我们会尽量多举例进行说明。建议大家边阅读边思考&#xff0c;如果还能边实践就更好了。 1. 表达式 说到表达式&#xff0c;大家可能感觉有些陌生&#xff0c;我们先来举个…