• C++ 环境变量 二


    目录

    获取环境变量的后两种方法

    环境变量具有全局属性

    内建命令

    和环境变量相关的命令

    c语言访问地址

    重新理解地址

    地址空间


    获取环境变量的后两种方法

    main函数的第三个参数 :char* env[ ]

    也是一个指针数组,我们可以把它的内容打印出来看看。

    1. #include
    2. #include
    3. #include
    4. E>int main(int argc,char* argv,char* env[])
    5. {
    6. int i=0;
    7. for(; env[i];i++)
    8. {
    9. E> printf("env[%d]:%s\n",i,env[i]);
    10. }
    11. return 0
    12. }

    我们再把pid也打印出来看看:

    输入命令:

    !man getpid
    

    我们把这两个头文件加上

    1. #include
    2. #include
    3. #include
    4. #include
    5. E>#include
    6. E>int main(int argc,char* argv,char* env[])
    7. {
    8. int i=0;
    9. for(; env[i];i++)
    10. {
    11. E> printf("pid:%d,env[%d]:%s\n",getpid(),i,env[i]);
    12. }
    13. return 0
    14. }

     就可以把我们的此程序的环境变量打印出来:

    这个获取环境变量的方法和我们输入"env"命令获取的环境变量是一样的:

    我们之前说过如果我们把PATH置空,很多系统命令都用不了了:

    但是我们重启xshell之后就又可以用了。这是因为我们置空的是加载进内存的PATH。

    我们重启之后系统解释器会重新读取环境变量表,形成新的环境变量,环境变量是脚本配置文件的形式存在的的。

    在家目录下有个隐藏文件,bash_profile.

    环境变量表就在这个文件里面。

    假设我们自己写一个环境变量,

    然后用env调用环境变量,通过管道只打印我们自己写的环境变量,显示找不到:

    这是因为我们写的环境变量并没有被加载进环境变量表。

    我们可以通过 "export"命令把我们写的环境变量加载进环境变量表里面,再通过env打印就可以打印出来了。

    但是我们重启xshell之后我们配置的这个环境变量仍然会消失不见。只有当我们去家目录下面的bash_profile文件下写入我们的环境变量才能再次重启之后仍然会存在。

    vim ~/.bash_profile
    

    此刻我们就可以把我们自定义的变量打印出来看看:

    假如我们不想给main函数传参呢?

    有个外部变量叫   environ

     它指向了char * env[ ]

     我们把environ打印出来,main函数不带参照样可以把环境变量打印出来。

    1. int main()
    2. {
    3. extern char** environ;
    4. int i=0;
    5. for(;environ[i];i++)
    6. {
    7. printf("%d,%s",i,environ[i]);
    8. }
    9. return 0;
    10. }

    到目前为止,我们获取环境变量的方法有:

    main函数传参         getenv[  ]                 char** environ[  ] 

    环境变量具有全局属性

    我们在我们将才的进程里再写个子进程,看它能获取我们子进程的环境变量吗

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. pid_t id=fork();
    9. if(id==0)
    10. {
    11. extern char** environ;
    12. int i=0;
    13. for(;environ[i];i++)
    14. {
    15. printf("%d,%s",i,environ[i]);
    16. }
    17. }
    18. sleep(3);
    19. return 0
    20. }

     照样可以,这也证明了环境变量具有全局属性。

    我们自定一个本地变量

    OUR_ENV=333

     然后我们再把这个本地变量打印出来

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. printf("OUR_ENV_ENV:%S\n",getenv("OUR_ENV"));
    9. return 0
    10. }

     注意:因为是子进程,所以我们没export的时候子进程获取不到我们自定义的本地变量,会显示为null

    当我们export把我们定义的环境变量载入bash之后,让它成为环境变量,环境变量具有全局性,子进程就可以获取到了:

    内建命令

    我们再写几个本地变量:

    我们发现,a,b都为本地变量,echo是一个子进程,但是可以直接打印本地变量 a,b,这是为什么?

    再比如,我们把PATH置空,这时候ls,touch这种命令都用不了了,但是echo还可以用

    这是因为echo是shell的内置函数,这种命令叫做linux的内建命令。

    内建命令不创建子进程。

    和环境变量相关的命令

     set:

    把本地变量和环境变量全部打印出来:

    1. set | grep+本地变量名
    2. 功能:打印本地变量

    c语言访问地址

    程序的地址空间遵守的就是下面这张图:

    我们把这各个区的存的值的地址打印出来看看:

    1. int usa;
    2. int bbb=100;
    3. int main ()
    4. {
    5. printf("公共代码区:%p\n",main);
    6. const char* str="helo djwd";
    7. //常量区
    8. printf("常量区:%p\n",str);
    9. printf("初始化全局数据区:%p\n",&bbb);
    10. W>printf("未初始化全局数据区:%p\n",usa);
    11. char* heap=(char*)malloc(100);
    12. printf("堆区:%p\n",heap);
    13. printf("栈区:%p\n",&str);

     观察打印出来的地址可以发现一个问题,从公共代码区到常量区,再往下走,地址都是呈递增状态。

    当到了堆区之后,从堆区到栈区,中间宽度变的特别大。

    按照这个结果我们可以有这种推论:

    得出结论:堆和栈相对而生。

    验证

    把堆区地址打印看一下:

    1. char* heap1=(char*)malloc(100);
    2. char* heap2=(char*)malloc(100);
    3. char* heap3=(char*)malloc(100);
    4. char* heap4=(char*)malloc(100);
    5. printf("堆区:%p\n",heap);
    6. printf("堆区:%p\n",heap1);
    7. printf("堆区:%p\n",heap2);
    8. printf("堆区:%p\n",heap3);
    9. printf("堆区:%p\n",heap4);

    可以发现堆区越来越大,这也证明堆区向上增长

    把栈区地址打印一下看一下:

    1. char* str="hello";
    2. char* heap1=(char*)malloc(100);
    3. char* heap2=(char*)malloc(100);
    4. char* heap3=(char*)malloc(100);
    5. char* heap4=(char*)malloc(100);
    6. printf("栈区:%p\n",&str);
    7. printf("栈区:%p\n",&heap);
    8. printf("栈区:%p\n",&heap1);
    9. printf("栈区:%p\n",&heap2);
    10. printf("栈区:%p\n",&heap3);
    11. printf("栈区:%p\n",&heap4);

    观察图我们发现栈区越往下越小

    如果定义一个结构体object ,里面有三个成员变量 int a,b,c,abc谁最大?

    1. struct d
    2. {
    3. int a;
    4. int b;
    5. int c;
    6. }object;
    7. printf("%p\n",&object.a);
    8. printf("%p\n",&object.b);
    9. printf("%p\n",&object.c);

    很明显,c最大

    这是因为虽然栈区向下增长,但整体是向上增长的:

    假设我们定义一个 int b,一个int类型有4个字节,那就应该有4个地址,但是我们打印b的地址,显示出来只有一个,这个地址就是最小的地址。

    然后向上按照int类型访问4个字节,访问到最大字节:

    类型的本质叫做偏移量

    c语言中就是以起始位置+偏移量访问任何地址。

    定义一个int a,打印a的地址。

    a因为是常量,所以在栈区,又因为后开辟,所有地址偏小。

     现在我们a重定义为 static int a,此刻再重新打印a。我们发现a的地址变量,它和全局变量的地址是一个样子,也就是此时a就是一个全局变量:

    所以我们可以说已初始化全局变量区就是静态区:

    在栈区之上还有一个环境变量区和命令参数区:

    重新理解地址

    地址空间

    写一段如下代码:

    定义一个全局变量 g_val,赋值为123,创建一个子进程。

    父子进程运行,当运行五秒之后让子进程把g_val的值改为200,

    此时把父子进程的地址和g_val值都打印出来看看:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include // 添加头文件
    7. int g_val = 100;
    8. int main()
    9. {
    10. pid_t id = fork();
    11. if(id == 0)
    12. {
    13. //child
    14. int cnt = 5;
    15. while(1)
    16. {
    17. printf("child, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
    18. sleep(1);
    19. if(cnt == 0)
    20. {
    21. g_val=200;
    22. printf("child change g_val: 100->200\n");
    23. }
    24. cnt--;
    25. }
    26. }
    27. else
    28. {
    29. //father
    30. while(1)
    31. {
    32. printf("father, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
    33. sleep(1);
    34. }
    35. }
    36. sleep(100);
    37. return 0;
    38. }

    运行结果如下图,当进程跑了五秒之后g_val的值就被子进程改为200了

    注意此时父进程的g_val值为100,子进程的g_val值为200,但是请注意它们的地址:

    竟然是同一个地址

    同一个地址的怎么会有两个不一样的值

    解析

    一个地址不可能有两个值,因此可见这肯定不是一个物理地址,实际上这是一个虚拟地址。

    虽然是虚拟地址但是我们知道,进程都是要被加载进内存里面的,所以尽管它是虚拟地址,但是它肯定还是要在内存里放着的,而在内存里放着就必然有一个物理地址。

    因此这个虚拟地址和这个物理地址之间必然有某种联系,这种联系通过映射来表现。

    即把虚拟地址映射为物理地址,而这一映射都放在页表里面,一个虚拟地址映射一个物理地址。

    如下图表示:
     

    g_val是一个全局变量,并且被赋值为123,所以它在已初始化全局常量区:

    子进程有一部分属性个性化外(id之类的),其他的pcb属性基本上全都复用父进程的pcb属性。

    同样的虚拟地址和页父进程也要给子进程复制一份。

    此时子进程修改g_val的值为123

    因为g_val是全局变量,所以子进程修改了g_val那么父进程的g_val肯定也会被修改,但是进程之间是要保持相对独立性的,为了保证父进程不被影响。

    所以把value值拷贝一份给子进程,那么子进程修改的就是自己的g_val,不会影响父进程的g_val。

    因此我们可以看见父子进程的g_val值不一样,但是却是同一个地址的现象了,这个地址就是虚拟地址

  • 相关阅读:
    debian 12 PXE Server 批量部署系统
    新能源汽车产量创历史新高,七个月顶一年,江苏“爆炸式”发展?
    一次js请求一般情况下有哪些地方会有缓存处理
    E - Madoka and The Best University(数论/欧拉函数)
    k8s、调度约束
    2021年山东省职业院校技能大赛中职组”网络安全“正式赛题
    4.3 Go中的字符串及派生类型
    了解过的国内名师
    RocketMQ安装部署
    一本通1083;计算星期几
  • 原文地址:https://blog.csdn.net/m0_65143871/article/details/134041964