3.1 程序与进程的存储结构

linux平台下可执行文件的类型都是普通文件。文件的格式主要有三种:a.out,COFF,和ELF。需要注意的是,此a.out不等同于gcc filename不指定可执行文件名所得的名为a.out的可执行文件。二者名字相同,前者为可执行文件文件类型,后者仅为默认文件名,其世界的格式仍为ELF。
可执行文件在执行时,都将会演变成至少一个进程,操作系统也会将可执行文件的内容从磁盘复制到内存中。不论可执行文件的格式是什么,都包含以下几个部分:代码段,数据段,BSS(block started by symbol)段。其中,代码段主要保存程序执行代码,通常是只读的,在多个进程之间可以共享代码段;数据段保存已经初始化的全局变量、静态变量;BSS段保存未初始化的全局变量、静态变量,并在程序执行之前被内核初始化为0或者null。
image.png
运行于基于冯·诺依曼体系结构系统上的程序,数据和程序是一起存储了。因此,编译时,编译器会将全局变量的初始化数据捆绑到最终生成的程序文件中,而对于未初始化的全局变量只是为其分配(指示)了存储位置,不会将大量的0捆绑到程序中。
image.png
Test1实质上没有初始化全局变量,编译时编译器只是为了init_array_g1[]指出了将要使用的内存位置,而不发生 数据绑定。Test2则不同,它将init_array_g1[0]初始化为1,其它元素全部初始化为0,因此,编译器将把 init_array_g1[]数组的10000000个元素的初始化数据全部捆绑到最终的可执行文件中,导致编译后的文件十分庞大。
而当可执行文件演变产生一个新的进程之后(父进程为shell),进程在内存中的主要映像分为:代码段、数据段、BSS段、堆、栈。其中,堆是动态分配的内存区域,大小不固定,可动态增加或删减,C语言可用malloc函数和free函数进行动态内存的分配与释放;栈是用来保存函数临时创建的局部变量,很显然不包括函数内部的static变量。此外,函数的参数会被压入发起调用函数的进程栈中,调用结束后,返回值也会被存放进栈中。
同样需要注意的是,栈空间由系统自动分配和释放;堆空间则需要手动申请释放。
数据段、BSS段的变量生存周期为整个进程运行过程。
局部变量生存周期为函数内部,即申请到释放该段栈空间。
image.png

3.2 变量的类型修饰符

常用的类型修饰符有auto,const,register,static,extern。其中static和extern可用于定义函数。
变量的数据类型主要说明该变量占用的内存空间大小,类型修饰符指明该变量的存储位置。
auto不用显式写出,即没有指定类型修饰符的变量都是auto变量。
const修饰的变量是只读的,但不是常量。
image.png
如下图所示,可区分不同修饰符所对应变量在内存中的不同位置。
image.png

3.3 命令行参数及获取

3.3.1 命令行参数

C语言使用main函数作为程序执行的入口。由于程序都从main函数开始执行,所以命令行输入的信息将被main函数接收,也称为命令行参数。
main函数原型有几种形式:

  1. int main(void)
  2. int main(int argc,char *argv[])
  3. int main(int argc,char *argv[],char *envp[])

如果程序需要获取用户在运行程序指定的命令行参数,就需要main函数带参数。如果不需要就不用带形参。
main函数的前两个参数argc和argv传递了命令行参数信息。在命令行输入的信息通常由空格和tab隔开。
image.png
image.png
举例说明,执行./test -a bb “helloworld”
当test程序执行后,传入main函数的argc值为4:argv[0]指向”./test”,argv[1]指向”-a”,argv[2]指向“bb”,argv[3]指向”helloworld”,argv[4]值为null。
image.png

3.3.2 getopt获取命令行参数

在编程过程中,如果单纯使用main函数的参数argc和argv进行命令的判断,在简单的程序中可行,但在命令选项很多,多个命令前后次序不同会造成相同的结果情况下,使用argc和argv进行判断就会使得程序的分支判断不正确。此时我们需要使用getopt函数进行判断。
image.png
optstring参数是一个选项字符的字符串,其中每个字符都是选项字符。如果选项字符后面跟冒号,说明该选项字符需要指定接收一个参数,该参数可紧挨着选项字符后面也可以以空格隔开,并将该参数的指针赋给optarg;如果选项字符后面跟着两个冒号::,表示该选项字符后需要接受一个参数,且必须紧挨着选项字符后面,不能以空格隔开,并将该参数的指针赋给optarg。
image.png
一般是在循环结构中使用getopt函数以便遍历所有命令行的选项参数。在循环调用getopt函数结束后,命令行参数argv中的参数字符串按照顺序重新调整,将与选项参数有关的参数移到数组前部,其他参数移到数组后部:
image.png
image.png
image.png
image.png

3.4 环境变量

shell是一个特殊的进程,是用户与内核之间的接口。shell启动后拥有多个自己的变量,以列表的形式保存,这些变量也叫环境变量。
变量列表由若干字符串组成,并以null作为结尾。在shell启动进程之后,进程通过特殊的全局变量“char environ”继承shell环境变量。此变量不需用户自己定义,只需通过“extern char environ;”引用即可。
image.png
在shell中对环境变量的操作主要包括查看、定义、修改、删除。
查看:echo $变量名
查看当前shell下所有的环境变量信息,用set命令。
常见的环境变量有HOME(用户主目录),PWD(当前工作目录),PATH(默认可执行程序搜索路径)。

定义: 变量名=变量值(=中间不能有空格)
但是这样定义的环境变量只能被shell本身访问,如果在变量前加上export命令,则该变量就可以被shell中启动的其他应用程序访问。

修改:与定义环境变量格式相同,对export的使用也相同。

删除:unset 变量名

需要注意的是,使用命令方式对变量做出的改变,无论增加,删除,修改等都是临时的,当系统重新启动后就会恢复到改变之前的状态。这是因为linux系统启动过程中通过读取系统配置文件来确定各变量的值,然而使用命令方式对环境列表的改动并没有写入文件。
如果用户想要系统重启后依然保持对系统变量列表的改动,需要将改动写入/etc/profile和用户主目录中的.bash_profile。前者文件中的修改结果可以由所有登录用户访问,后者文件的修改只能被指定的用户访问。

在编程中访问进程的环境变量需要使用专门的函数,比如getenv,putenv,setenv,unsetenv。
image.png

3.6 错误代码

在linux/unix系统中,返回值为整型的函数执行出错时通常返回一个负值,返回值为指针类型的函数执行出错时通常返回null指针。可用全局的整型变量errno查看错误代码,不需要自行定义,只需要引用errno.h头文件即可。
默认情况下errno值为0。
C标准定义了两个函数帮助打印errno对应的错误原因:一个是strerror,另一个是perror。
image.png
image.png

3.7 标准I/O和文件I/O

对文件的输入/输出一般有两种形式,一个是标准I/O,一个是文件I/O。
标准I/O与文件I/O最大的区别在于标准I/O有用户缓冲区,文件I/O没有。使用用户缓冲区是为了尽可能减少使用系统调用read和write的次数。标准I/O默认使用缓冲机制,比如调用fopen函数,不仅打开一个文件,还要建立一个缓冲区,一个包含文件和缓冲区相关数据的数据结构。文件I/O一般没有缓冲区,需要自己创建缓冲区,在linux系统中可使用内核缓冲技术来提高效率。
读写调用是在内核缓冲区和进程缓冲区之间进行数据复制。
标准I/O提供三种类型的缓冲:

  1. 全缓冲:在填满标准I/O缓冲区之后才进行实际的I/O操作。通常对驻留在磁盘上的文件由标准I/O库实行全缓冲。image.png
  2. 行缓冲:当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。因为标准I/O库中用来收集每一行的缓冲区长度相等,所以只要填满缓冲区,不管有无换行符,也进行I/O操作。
  3. 无缓冲:标准I/O库不对字符进行缓冲存储。比如标准错误输出。

一般来说,对于磁盘文件的读写操作时全缓冲的,对显示器等终端设备是行缓冲的,对标准错误输出是无缓冲的