• argv:是一个指针数组,里面的每一个元素指向的是字符串.
  • argc:指针数组的元素个数.

那么有的uu会好奇,这个指针数组是用来干啥的,我们来看下面这段代码

代码1

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc,char *argv[])
{
    for(int i = 0; i < argc; i++)
    {
        printf("argv[%d]->%s\n",i,argv[i]);
    }
}

  • 我们可以清晰地看到,操作系统将我们输入的命令行参数拆成了字符串放到了argv这个指针数组中.
  • 有些操作系统的功能会将用户输入的命令行参数空格为分隔符(将这些空格换成\0,这样子就将大字符串拆分成了一个一个小字符串)拆分成字符串放到argv这个数组中.

那么有的uu会好奇,这个指针数组是以什么结尾的呢?其实是NULL,我们来验证一下

代码2

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc,char *argv[])
{
    for(int i = 0; argv[i]; i++)
    {
        printf("argv[%d]->%s\n",i,argv[i]);
    }
}


有的uu会好奇,为什么要有命令行参数呢,我们来看下面这段代码

代码3

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        printf("Usage:%s -[a,b,c,d]\n",argv[0]);
        return 1;
    }
    else if(strcmp(argv[1],"a") == 0)
    {
        printf("This is Function 1\n");
    }
     else if(strcmp(argv[1],"b") == 0)
    {
        printf("This is Function 2\n");
    }
     else if(strcmp(argv[1],"c") == 0)
    {
        printf("This is Function 3\n");
    }
     else if(strcmp(argv[1],"d") == 0)
    {
        printf("This is Function 4\n");
    }
    else
    {
        printf("No This Function\n");
    }
    return 0;
}

我们可以看到,当输入不同的命令行参数时,会执行不同的功能,所以命令行参数的本质是

  • 交给程序的不同的选型,用来定制程序功能,根据场景的需求来选择该程序里面的不同功能.

1:环境变量

1.1:基本概念
  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

  • 环境变量通常具有某些特殊用途,并且在系统当中通常具有全局特性。
1.2常见环境变量
  • PATH:指定命令的搜索路径.
  • HOME: 指定用户的主工作目录(即用户登录到Linux系统中的默认所处目录)。
  • SHELL: 当前Shell,它的值通常是/bin/bash。
1.3:查看环境变量
  • echo $NAME //NAME为待查看的环境变量名称

1.4:测试PATH
代码1

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
    printf("hello world\n");
}

我们可以看到,当执行自己的可执行程序时一定要带路径,那么这是为什么呢

  • 因为在Linux中,存在一些全局的设置,表明并且告诉命令行解释器,应该在哪些路径下去寻找对应的可执行程序.

那么有没有什么办法可以通过不带路径去执行自己的可执行程序呢,其实是有的.

当我们将自己可执行程序所在的路径导入到环境变量中,此时再去执行自己的可执行程序就不用再带路径了.并且我们也可以看到,当查看环境变量PATH以后,此时也多了我们自己的可执行程序的路径.

重启前

重启后

但是,当我们重启Xshell以后,会发现此时PATH路径又重新回来了,那么这是为什么呢

  • 系统中的很多配置,在我们登陆Linux系统的时候,就已经加载到了bash进程中,而实际对应的bash进程它就在内存中.
  • bash进程默认我们查到的环境变量是内存级的,即使在Xshell中改变了PATH的路径,但只要关闭重启Xshell,路径就能重新回来了.
  • 那么为什么可以重新回来,原因在于,最开始的环境变量不是在内存中,而是在系统对应的配置文件中.
  • 所以环境变量它就是系统的配置文件,当操作系统或者bash进程在启动的时候,它会把环境变量从配置文件导入到操作系统内部或者bash解释器里面.

路径的分隔符

有的uu会比较好奇,为什么这些路径是以:为分隔符呢

  • 因为bash在执行命令的时候,需要先找到命令,因为未来需要加载并且执行命令.
1.5:查看其他环境变量

2:环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串.并且结尾以NULL结束

2.1:代码1

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[],char * env[])
{
    for(int i = 0;env[i]; i++)
    {
         printf("%s\n", env[i]);
    }
    return 0;
}

2.2:代码2

代码语言:javascript

AI代码解释

#include <stdio.h>
int main(int argc, char *argv[])
{
    //libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明
    extern char **environ;
    int i = 0;
    for(; environ[i]; i++)
    {
        printf("%s\n", environ[i]);
    }
    return 0;
}

我们可以看到,这些环境变量,就是刚刚Shell内部的环境变量,而我们通过代码的方式获取到了,说明环境变量是默认可以被子进程拿到的(在命令行执行的可执行程序都是bash的子进程).

3:环境变量具有全局属性

  • 环境变量具有全局属性,可以被子进程继承下去

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <stdlib.h>
int main()
{
     char * env = getenv("MYENV");
     if(env){
     printf("%s\n", env);
     return 0;
}

我们可以发现,当将MYENV环境变量导入以后,此时再运行程序就有了,说明环境变量是可以被子进程继承下去的.

有的uu就会好奇,export也是条命令,那么在使用的时候为什么不会创建子进程呢?因为如果创建的了子进程,那么导入的环境变量就不应该被bash看到,像echo export等等都是内建命令,这是由bash亲自来执行的,而百分之80的命令都是由bash创建子进程来执行的,像ps这些.

4:程序地址空间

在C语言阶段,相信大家见过下面的空间布局图,可是uu们对这个并不了解,下面我们先通过一段代码来感受一下.

代码1

代码语言:javascript

AI代码解释

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int global_value = 100;

int main()
{
  
  printf("Parent process is running and my pid = %d,ppid%d\n",getpid(),getppid());
  pid_t id = fork();
  //返回0则表示的是子进程
  if(id == 0)
  {
    //child
    int count = 0; 
    while(1)
    {
      printf("I am child process and my pid = %d,ppid = %d,global_value = %d,&global_value = %p\n",getpid(),getppid(),global_value,&global_value);
      sleep(1);
      count++;
      if(5 == count)
      {
        global_value = 300;
        printf("I am child process,change %d--->%d\n",100,global_value);
      }
    }
  }
  else
  {
    //parent
    while(1)
    {
      printf("I am parent process and my pid = %d,ppid = %d,global_value = %d,&global_value = %p\n",getpid(),getppid(),global_value,&global_value);
      sleep(1);
    }
  }

  return 0;
}

我们可以清晰地看到,在global_value修改前,父子进程输出来的变量值和地址是一模一样的,这个很好理解,因为子进程会以父进程为模板,父子并没有对变量进行任何修改. 但是当子进程对global_value进行修改时,我们会发现输出的地址是一样的,但是变量内容不一样,那么可以得出以下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量.
  • 地址值是一样的,说明该地址不是物理地址,因为物理地址是唯一的.
  • 在Linux下,这种地址叫做虚拟地址.
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址物理地址,用户一概看不到,由OS统一管理.
  • OS必须负责将虚拟地址转换为物理地址.


 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐