目录
获取环境变量的后两种方法
main函数的第三个参数 :char* env[ ]
也是一个指针数组,我们可以把它的内容打印出来看看。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
E>int main(int argc,char* argv,char* env[])
{
int i=0;
for(; env[i];i++)
{
E> printf("env[%d]:%s\n",i,env[i]);
}
return 0;
}
我们再把pid也打印出来看看:
输入命令:
!man getpid
我们把这两个头文件加上
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
E>#include<unistd.h>
E>int main(int argc,char* argv,char* env[])
{
int i=0;
for(; env[i];i++)
{
E> printf("pid:%d,env[%d]:%s\n",getpid(),i,env[i]);
}
return 0;
}
就可以把我们的此程序的环境变量打印出来:
这个获取环境变量的方法和我们输入"env"命令获取的环境变量是一样的:
我们之前说过如果我们把PATH置空,很多系统命令都用不了了:
但是我们重启xshell之后就又可以用了。这是因为我们置空的是加载进内存的PATH。
我们重启之后系统解释器会重新读取环境变量表,形成新的环境变量,环境变量是脚本配置文件的形式存在的的。
在家目录下有个隐藏文件,bash_profile.
环境变量表就在这个文件里面。
假设我们自己写一个环境变量,
然后用env调用环境变量,通过管道只打印我们自己写的环境变量,显示找不到:
这是因为我们写的环境变量并没有被加载进环境变量表。
我们可以通过 "export"命令把我们写的环境变量加载进环境变量表里面,再通过env打印就可以打印出来了。
但是我们重启xshell之后我们配置的这个环境变量仍然会消失不见。只有当我们去家目录下面的bash_profile文件下写入我们的环境变量才能再次重启之后仍然会存在。
vim ~/.bash_profile
此刻我们就可以把我们自定义的变量打印出来看看:
假如我们不想给main函数传参呢?
有个外部变量叫 environ
它指向了char * env[ ]
我们把environ打印出来,main函数不带参照样可以把环境变量打印出来。
int main()
{
extern char** environ;
int i=0;
for(;environ[i];i++)
{
printf("%d,%s",i,environ[i]);
}
return 0;
}
到目前为止,我们获取环境变量的方法有:
main函数传参 getenv[ ] char** environ[ ]
环境变量具有全局属性
我们在我们将才的进程里再写个子进程,看它能获取我们子进程的环境变量吗
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t id=fork();
if(id==0)
{
extern char** environ;
int i=0;
for(;environ[i];i++)
{
printf("%d,%s",i,environ[i]);
}
}
sleep(3);
return 0;
}
照样可以,这也证明了环境变量具有全局属性。
我们自定一个本地变量
OUR_ENV=333
然后我们再把这个本地变量打印出来
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("OUR_ENV_ENV:%S\n",getenv("OUR_ENV"));
return 0;
}
注意:因为是子进程,所以我们没export的时候子进程获取不到我们自定义的本地变量,会显示为null
当我们export把我们定义的环境变量载入bash之后,让它成为环境变量,环境变量具有全局性,子进程就可以获取到了:
内建命令
我们再写几个本地变量:
我们发现,a,b都为本地变量,echo是一个子进程,但是可以直接打印本地变量 a,b,这是为什么?
再比如,我们把PATH置空,这时候ls,touch这种命令都用不了了,但是echo还可以用
这是因为echo是shell的内置函数,这种命令叫做linux的内建命令。
内建命令不创建子进程。
和环境变量相关的命令
set:
把本地变量和环境变量全部打印出来:
set | grep+本地变量名
功能:打印本地变量
c语言访问地址
程序的地址空间遵守的就是下面这张图:
我们把这各个区的存的值的地址打印出来看看:
int usa;
int bbb=100;
int main ()
{
printf("公共代码区:%p\n",main);
const char* str="helo djwd";
//常量区
printf("常量区:%p\n",str);
printf("初始化全局数据区:%p\n",&bbb);
W>printf("未初始化全局数据区:%p\n",usa);
char* heap=(char*)malloc(100);
printf("堆区:%p\n",heap);
printf("栈区:%p\n",&str);
观察打印出来的地址可以发现一个问题,从公共代码区到常量区,再往下走,地址都是呈递增状态。
当到了堆区之后,从堆区到栈区,中间宽度变的特别大。
按照这个结果我们可以有这种推论:
得出结论:堆和栈相对而生。
验证
把堆区地址打印看一下:
char* heap1=(char*)malloc(100);
char* heap2=(char*)malloc(100);
char* heap3=(char*)malloc(100);
char* heap4=(char*)malloc(100);
printf("堆区:%p\n",heap);
printf("堆区:%p\n",heap1);
printf("堆区:%p\n",heap2);
printf("堆区:%p\n",heap3);
printf("堆区:%p\n",heap4);
可以发现堆区越来越大,这也证明堆区向上增长
把栈区地址打印一下看一下:
char* str="hello";
char* heap1=(char*)malloc(100);
char* heap2=(char*)malloc(100);
char* heap3=(char*)malloc(100);
char* heap4=(char*)malloc(100);
printf("栈区:%p\n",&str);
printf("栈区:%p\n",&heap);
printf("栈区:%p\n",&heap1);
printf("栈区:%p\n",&heap2);
printf("栈区:%p\n",&heap3);
printf("栈区:%p\n",&heap4);
观察图我们发现栈区越往下越小
如果定义一个结构体object ,里面有三个成员变量 int a,b,c,abc谁最大?
struct d
{
int a;
int b;
int c;
}object;
printf("%p\n",&object.a);
printf("%p\n",&object.b);
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值都打印出来看看:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h> // 添加头文件
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
int cnt = 5;
while(1)
{
printf("child, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
if(cnt == 0)
{
g_val=200;
printf("child change g_val: 100->200\n");
}
cnt--;
}
}
else
{
//father
while(1)
{
printf("father, Pid: %d, Ppid: %d, g_val: %d, &g_val=%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
sleep(100);
return 0;
}
运行结果如下图,当进程跑了五秒之后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值不一样,但是却是同一个地址的现象了,这个地址就是虚拟地址