7.5 环境表

7.5 环境表

每个程序都接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的字符串的地址。全局变量environ则包含了该指针数组的地址。

extern char **environ;

例如:如果该环境包含五个字符串,那么它看起来可能如图7 – 2中所示。

7-2 由五个字符串组成的环境、

clip_image002

其中,每个字符串的结束处都有一个null字符。我们称environ为环境指针,指针数组为环境表,其中各指针指向的字符串为环境字符串。

按照惯例,环境由:

name = value

这样的字符串组成,这与图7 – 2中所示相同。大多数预定义名完全由大写字母组成,但这只是一个惯例。

在历史上,大多数U N I X系统对main函数提供了第三个参数,它就是环境表地址:

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

因为ANSI C规定main函数只有两个参数,而且第三个参数与全局变量environ相比也没有带来更多益处,所以POSIX . 1也规定应使用environ而不使用第三个参数。通常用getenvputenv函数( 7 . 9节将说明)来存取特定的环境变量,而不是用environ变量。但是,如果要查看整个环境,则必须使用environ指针。

7.4 命令行参数

7.4 命令行参数

当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。这是UNIX shell的一部分常规操作。在前几章的很多实例中,我们已经看到了这一点。

程序7 – 3将其所有命令行参数都回送到标准输出上。注意,通常(UNIX echo(1)程序不回送第0个参数)

#include <stdio.h>



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

{

int i;

for (i = 0; i < argc; i++)

// for (i = 0; argv[i] != NULL, i++)

printf("argv[%d] = %sn", i, argv[i]);

return 0;

}

      如果编译该程序并将可执行程序文件命名为echoarg,那么

$./echoarg hello world

argv[0] = ./echoarg

argv[1] = hello

argv[2] = world

由于ISO CPOSIX . 1都要求a rgv [argc] 是一个空指针。这就使我们可以将参数处理循环改写为:

for(i = 0; argv[i] != NULL; i++)

7.3 进程终止

7.3 进程终止

8种方式使进程终止(termination),其中5中为正常终止,他们是:

l  main返回;

l  调用exit

l  调用_exit_Exit;

l  最后一个线程从其启动例程返回;

l  最后一个线程调用pthread_exit

异常终止有3种方式,他们是:

l  调用abort

l  接收一个信号并终止;

l  最后一个线程对取消请求做出响应。

 

上节提及的起动例程是这样编写的,使得从main返回后立即调用exit函数。如果将启动例程以C代码形式表示(实际上该例程常常用汇编语言编写),则它调用main函数的形式可能是:

exit( main(argc, argv) );

7.3.1 exit_ exit函数

有三个函数用于正常终止一个程序:_exit _Exit 立即进入内核exit 则先执行一些 清理处理(包括调用执行各终止处理程序,关闭所有标准 I/O ),然后进入内核。

#include <stdlib.h>

void exit(int s t a t u s) ;

void _Exit(int status);

#include <unistd.h>

void _exit (ints t a t u s) ;

我们将在8 . 5节中讨论这三个函数对其他进程,例如终止进程的父、子进程的影响。

使用不同头文件的原因是:exit_Exi是由ANSI C说明的,而_ exit则是由POSIX . 1说明的。

由于历史原因, exit函数总是执行一个标准I/O库的清除关闭操作:为所有打开流调用fclose函数。回忆5.5节,这造成缓存中的所有数据都被刷新(写到文件上)

三个exit函数都带一个整型参数,称之为终止状态(exit status)。大多数UNIX shell都提供检查一个进程终止状态的方法。如果(a)若调用这些函数时不带终止状态,或(b)main执行了一个无返回值的return语句,或(c)main没有声明返回类型为整型,则该进程的终止状态是末定义的。

但是,若main的返回类型是整型,并且main执行到最后一条语句时返回(隐式返回),那么该进程的终止状态为0

main函数返回一个整型值与用该值调用exit是等价的。于是在main函数中

exit0);等价于return 0

这就意味着,下列经典性的C语言程序:

#include <stdio.h>

main ()

{

printf ("hello, world n");

}

是不完整的,因为main函数没有使用return语句返回(隐式返回),它在返回到C的起动例程时并没有返回一个值(终止状态)。另外,若使用:

return ( 0 ) ;

或者

exit ( 0 )

则向执行此程序的进程(常常是一个s h e l l进程)返回终止状态0。另外,main函数的说明实际上应当是:

int main(void)

对上述程序进行编译,然后运行,则可见其终止码(echo $?)是随机的。如果在不同的系统上编译该程序,我们很可能得到不同的终止码,这取决与main函数返回时栈和寄存器的内容。

main说明为返回一个整型以及用exit代替return,对某些C编译程序和UNIX lint ( 1 )程序而言会产生不必要的警告信息,因为这些编译程序并不了解main中的exitreturn语句的作用相同。警告信息可能是“ control reaches end of nonvoid f u n c t i o n(控制到达非v o i d函数的结束处)”。避开这种警告信息的一种方法是:在main中使用return语句而不是exit。但是这样做的结果是不能用U N I Xg r e p公用程序来找出程序中所有的exit调用。另外一个解决方法是将main说明为返回v o i d而不是i n t,然后仍旧调用exit。这也避开了编译程序的警告,但从程序设计角度看却并不正确。本章将main表示为返回一个整型,因为这是ANSI CPOSIX . 1所定义的。我们将不理会编译程序不必要的警告。

下一章将了解进程如何使程序执行,如何等待执行该程序的进程完成,然后取得其终止状态。

7.3.2 atexit函数

按照ISO C的规定,一个进程可以登记多至3 2个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exit handler),并用atexit函数来登记这些函数。

#include <stdlib.h>

int atexit(void (*func) ( v o i d ) ) ;

返回:若成功则为0,若出错则为非0

其中, atexit的参数是一个函数地址,当调用此函数时无需向它传送任何参数,也不期望它返回一个值。exit以登记这些函数的相反顺序调用它们。同一函数如若登记多次,则也被调用多次。

终止处理程序这一机制由ANSI C最新引进。S V R 44 . 3 + B S D都提供这种机制。系统V的早期版本和4 . 3 B S D则都不提供此机制。为了确定一个给定的平台支持的最大终止处理程序数,可以使用sysconf函数。

根据ISO CPOSIX . 1exit首先调用各终止处理程序,然后按需多次调用f c l o s e,关闭所有打开流。POSIX.1扩展了ISO c标准,它指定如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序。图7 – 1显示了一个C程序是如何起动的,以及它终止的各种方式。

7-1 一个C程序是如何起动和终止的(启动的_exit也可以为_Exit

clip_image002

注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(调用exit )调用_ exit_Exit。进程也可非自愿地由一个信号使其终止(7 – 1中没有显示)

实例

程序7 – 2说明了如何使用atexit函数。

#include <stdio.h>

#include <stdlib.h>



static void my_exit1(void);

static void my_exit2(void);



int main(void)

{

if (atexit(my_exit2) != 0)

perror("can not register my_exit2");

if (atexit(my_exit1) != 0)

perror("can not register my_exit2");

if (atexit(my_exit1) != 0)

perror("can not register my_exit2");



printf("main is donen");

return 0;

}



static void my_exit1(void)

{

printf("first exit handlern");

}



static void my_exit2(void)

{

printf("second exit handlern");

}

执行程序7 – 2产生:

main is done

first exit handler

first exit handler

second exit handler

      终止处理程序每登记一次,就会被调用一次。在上述程序中,第一个终止处理程序被登记两次,所以也会被调用两次。注意,main中没有调用exit,而是用了return语句