Linux C语言退出函数解析


Linux C标准定义了下面的退出函数:

#include <stdlib.h>
void exit(int status);
void _Exit(int status);
int atexit(void (*function)(void));

函数功能介绍如下:

void exit(int status)

该函数终止调用的程序。status传递给系统用于父进程恢复。程序退出之前,exit()调用所有以atexit()注册的函数,清空所有打开的<stdio.h> FILE*流的缓冲区并关闭流,然后删除所有由tmpfile()创建的临时文件。进程退出时,内核关闭所有剩下的已打开文件(即那些由open()、creat()或文件描述符继承打开的文件),释放其地址空间,然后释放所有其他使用的资源。exit()从不返回。

void _Exit(int status)

该函数基本上与POSIX的_exit()函数相同;我们稍后再对其进行介绍。

int atexit(void (*function)(void))

function是一个函数指针,指向程序退出时候调用的一个回调函数。exit()在其关闭文件和终止之前调用该回调函数。这个想法在于程序能够在最终关闭之前提供一个或者多个运行的清理函数。提供一个函数被成为注册该函数。

atexit()成功时返回0,出错时返回-1并设置相应的errno。

下面的程序没有有用的功能,但它演示了如何使用atexit():

void callback1(void){printf("callback called\n");}
void callback2(void)(printf("callback called\n");}
void callback3(void)(printf("callback called\n");}
int main(int argc,char** argv)
{
printf("registering callback1\n");atexit(callback1);
printf("registering callback2\n");atexit(callback2);
printf("registering callback3\n");atexit(callback3);
printf("exiting now\n");
exit(0);
}

下面是程序的运行结果:

$atexit
registering callback1
registering callback2
registering callback3
exiting now
callback3 called
callback2 called
callback1 called

正如上例所示,使用atexit()注册的函数运行时的顺序和注册的顺序相反:最近注册的最先运行(这也称为后进先出(last-in-first-out),缩写为LIFO)。

POSIX定义了_exit()函数。与exit()不同,exit()调用回调函数并进行<stdio.h>清理,_exit()是“立即死亡”的函数:

#include <unistd.h>

void _exit(int status);

_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已刷新的“出口函数”(由atexit定义)。

实际上,ISO C的_Exit()函数与_exit()相同。C函数指出_Exit()是否调用以atexit()注册的函数并关闭打开的文件取决与实现。对于GLIBC系统,可能不会,即_Exit()与_exit()表现相似。

使用_exit()的时机是在fork()产生的子进程中调用exec()失败的时候。这种情况下,不需要使用通常的exit(),因为它会清空所有由FILE*流保存的缓冲区数据。随后父进程清空其缓冲区拷贝时,导致缓冲的数据被写了两次;显然这不是很恰当。

例如,加入你运行了一个shell命令,并且自己调用fork()和exec()。代码可能如下所示:

char *shellcommand="...";
pid_t child;
if((child=fork())==0){
execl("/bin/sh","sh","-c",shellcommand,NULL);
_exit(errno==ENOENT?127:126);
}

errno测试和退出值采取了POSIX shell所使用的惯例。如果要求的程序没有退出(ENOENT——目录中没有它的项),则退出值为127。否则,文件同样退出,但由于其他原因不能够被exec()执行,则退出状态为126。在你自己的程序中采取这个惯例将会是个好主意。

简言之,为了更好地使用exit()和atexit(),你应该遵循一下规则:

1、定义一个较小的退出状态值的集合,你的程序使用该集合中的值与其调用者进行通信。在你的代码中使用#define常量或enum定义这些值。

2、决定是否有必要与atexit()一起使用回调函数。如果有必要,则在main()中适当地方注册这些函数;例如,在解析选项之后以及初始化任何回调函数可能清除的数据结构之后,记住函数以LIFO(last-in-first-out)顺序进行调用。

3、如果出错,在任一地方都可以使用exit()从程序退出,退出是能够发生的正确行为。同时使用你定义的错误代码。

4、main()函数是个例外,你可以在其中使用return。我们自己的风格是,通常出问题时使用exit(),而如果一切正常,在main()结尾处使用“return 0”。

5、如果调用exec()失败,则在子进程中使用_exit()或_Exit()。

相关内容