🔥keyipatience:个人主页

🎬作者简介:C/C++后端开发学习者

🌟专栏传送门:《c++》《linux》

⭐️patience is key in life

一.环境变量

基本概念

 • 环境变量(environmentvariables)⼀般是指在操作系统中用来指定操作系统运行环境的⼀些参数

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

• 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

命令行参数

1.main的参数

在Linux系统中,main函数的参数通常定义为int argc, char *argv[],用于接收命令行输入的参数。argc表示参数的数量,argv是一个指向参数字符串数组的指针。

  • argc(argument count):表示命令行参数的总数,包括程序名称本身。例如,执行./program arg1 arg2时,argc的值为3。
  • argv(argument vector):是一个字符串数组,存储所有命令行参数。argv[0]通常是程序名称,argv[1]argv[argc-1]是用户输入的参数。

eg:

2.main参数用法和用途

所以通过main函数的命令行参数是能够实现程序不同子功能的。

进而我们平常写的一些指令,就可以通过加选项的方式,也实现不同的功能。因为这些指令也是用c语言或其他语言写的,即通过main的命令行参数实现。

PATH环境变量

为什么我们自己写的程序要运行起来需要在前面加./ ,而执行系统命令的时候却不用

因为要执行一个文件必须先要找到它,所以./就表示这个程序在当前的路径下,而系统命令不用是因为系统存在环境变量,来帮助找到目标二级制程序

/usr/bin 是Linux系统中一个重要的目录,用于存放大多数用户命令和可执行文件

所以如果我们把自己写的程序拷贝到该目录下,就可以不用加./即可运行

但非常不建议这样做,因为我们自己写的程序可能会有问题,直接放到系统默认路径下,可能会污染系统本来的指令池

但现在问题又来了,系统怎么知道会去usr/bin/下寻找指令呢?

这是因为又PATH这个环境变量,用于指定可执行程序的搜索路径。当用户在终端输入命令时,系统会按照PATH中定义的路径顺序查找对应的可执行文件。

env查看所有的环境变量

可以看到环境变量的结构为环境变量的名称加内容

根据名称来查看一个环境变量的内容

可以看到PATH内容中有以:作为分隔符的路径,即path在查找时就是按照定义的顺序寻找命令对应的可执行文件

1.如何在存储的角度理解环境变量呢?

我们知道一个用户在登陆时,系统会为其创建一个bash进程,然后bash就会从系统当中读取所有的环境变量信息,然后在bash进程内部形成一张表(环境变量表)

这个环境变量表就是一个指针数组,而每一个环境变量就是一个字符串,在表中用0指针位置指向第一个环境变量,1指针指向第二个环境变量以此类推。所以在命令行输入指令的时候,这个指令先以字符串的形式被bash获取,然后bash先构建命令行参数表(argv)把命令解析,然后通过argv[0]知道程序名,再到环境变量表中找到PATH环境变量,进而在PATH下的子路径下和程序名找到命令对应的可执行文件。

2.环境变量存储在哪里?

bash是从系统哪里获取到所有的环境变量信息的呢?

就是从系统的相关配置文件(.bash_profile和.bashrc)中获取到的

3. 认识更多的环境变量

HOME

HOME变量存储当前用户的主目录路径。通常格式为/home/username,用于快速访问用户个人文件和配置。

SHELL

SHELL变量指定当前用户使用的默认Shell程序路径,如/bin/bash/bin/zsh

USER

USER变量记录当前登录用户的用户名,与whoami命令输出一致。

HISTSIZE

HISTSIZE变量控制Shell历史记录中保存的命令数量上限(linux下默认1000个)

HOSTNAME

HOSTNAME变量存储当前系统的主机名

PWD

PWD变量表示当前工作目录的绝对路径,随cd命令动态更新

OLDPWD

OLDPWD变量保存上一次工作目录的路径,便于通过cd -快速切换。

获取环境变量的方法

1.指令操作上

使用echo命令

通过echo命令结合变量名前加$符号,可以输出特定环境变量的值。

使用env命令

env命令可以列出当前会话的所有环境变量。

使用export命令

export命令通常用于添加环境变量。

使用getenv命令

如果需要获取特定环境变量的值,可以使用 getenv 函数
如果找到对应的环境变量,返回指向其值的指针;如果未找到,返回 NULL

eg:

 char *path = getenv("PATH");

使用unset命令

unset可以取消export添加的环境变量

2.代码上

main函数还有第三个参数

int main(int argc, char *argv[], char *envp[])

envp:环境变量的字符串数组(以 NULL 结尾)。

通过 envp 访问环境变量(环境变量在父子关系中的问题)

如果使用带 envp 的 main 函数签名,可以直接访问环境变量:

code:

#include <stdio.h>

int main(int argc, char *argv[], char *envp[]) {
    for (int i = 0; envp[i] != NULL; i++) {
        printf("%s\n", envp[i]);
    }
    return 0;
}
 

char*argv[]和char*envp这两个表分别是bash把解析好的命令行参数表给argv,环境变量表给envp即envp这个指针数组里的环境变量都是父进程bash给的。所以main函数获取到的环境变量是父进程(bash的)

现在我们通过export添加3个环境变量,是添加到bash进程的,通过env是可以看到的

s

并且再运行code也能够看到,因为code里面main函数中printf打印的是envp的内容呀,在上面说过envp的环境变量都是bash父进程给的。所以导入到父进程的环境变量是可以被子进程拿到,即环境变量可以被子进程继承,进而从第一个bash进程开始所有的子进程都可以获取到环境变量,这和环境变量在系统中具有全局特性就对应上了。

为什么要这样设计我们不妨再看一个例子

现在用getenv设计一个只想我自己一个人才能运行的程序

这里强转成void主要是因为argc,argv和env这些变量定义出来后并没有使用,编译的时候会有警告,所以强转成void是取消警告的一种方法

因为只有bash才能知道当前登录用户是谁,然后bash把这个环境变量信息传递给子进程,子进程根据这个信息做身份识别,所以只有ysm这个用户才能运行。

所以这样设计,可以让环境变量的相关信息让子进程继承下去,子进程就可以根据环境变量做各种个性化设计,同时也能保证进程执行上下文的一致性。

理解环境变量的特性

1.环境变量具有全局特性这个在前面我们已经知道

2.补充两个概念

本地变量

其实在bash内部会记录两套变量分别是环境变量和本地变量。

(1)本地变量仅在当前 Shell 会话中有效,子进程无法访问。通常用于临时存储数据或脚本内部使用。

(2)环境变量可以被当前 Shell 及其子进程访问,通常用于系统配置或程序运行时传递参数

(3)定义本地变量的语法:var_name=value。如果定义了本地变量后想要变成环境变量直接export var_name就可以了,(eg:不用再写i=10)

(4)查看所有变量(包括本地变量和环境变量):set(env只能看到环境变量)

(5)删除本地变量或环境变量:unset var_name

内建命令

如果照我们前面的理解话就有一个问题:我们导的环境变量是导在bash里,export相比于bash为子进程,那为什么可以把环境变量导给父进程呢?即子进程把环境变量给父进程了,不是说是子进程继承父进程的环境变量吗?

其实这个export叫内建命令,内建命令(built-in commands)是 Shell 直接提供的命令,无需调用外部程序,即不需要创建子进程,而是让bash自己亲自执行,一般是通过bash自己调用函数,或者系统调用完成。执行效率更高,适合频繁调用的操作。所以export时根本就没有创建子进程,不存在父子关系

程序地址空间(进程地址空间,虚拟地址空间)

进程地址空间的组成

  1. 代码段(Text Segment)
    存放可执行指令,通常是只读的,防止程序被意外修改。

  2. 数据段(Data Segment)

    • 初始化的全局变量和静态变量。
    • 未初始化的全局变量和静态变量(BSS段)。
  3. 堆(Heap)(向上增长)
    动态分配的内存区域,通过mallocnew等函数申请,由程序员手动管理或依赖垃圾回收机制。

  4. 栈(Stack)(向下增长)
    存放局部变量、函数参数和返回地址,由编译器自动管理,遵循后进先出(LIFO)原则。

  5. 共享库(Shared Libraries)
    多个进程共享的代码和数据,如C标准库,减少内存冗余。

  6. 内核空间(Kernel Space)
    操作系统内核代码和数据,用户进程无法直接访问。

在这里我们就先简单理解一点就是程序地址空间(进程地址空间,虚拟地址空间)不是内存!!

运行结果:

可以看到子进程的gval每一秒加1,而父进程的gval一直是100,这是写时拷贝在前面我们已经见过了,可是我们发现每一秒的父子进程的gval地址是一样的,如果是内存的话,那岂不是父子进程的gval应该是一样的吗,所以这不是内存地址,而是虚拟地址。所以程序地址空间也称为虚拟地址空间。(详细的后面慢慢讲到)


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐