进程结束正常结束1,从main函数返回
2、调用exit
3、调用_exit或_exit
4、最后一个线程从其启动例程返回
5、最后一个线程调用pthread_exit
异常结束1,调用abort
2、接到信号结束
3、最后一个线程响应取消请求
atexit ()挂接函数除了使用atexit()来实现钩子函数之外,还可以使用on_exit()来实现。
name atexit-registerafunctiontobecalledatnormalprocesstermination注册在程序成功结束时调用的函数。 synopsis # include stdlib.hint atexit (void (* function ) ) void ); escriptiontheatexit (功能注册服务订阅服务, eitherviaexit(3) orviareturnfromtheprogram ' s main ) .我们是atexit ) )函数中注册函数, 进程成功结束时(atexit ) )函数退出)3)或程序中的main通过了functionssoregisteredarecalledinthereverseorderoftheirregisteredarecalledintherederoftheireregiregion no arguments are passed .调用已注册函数的顺序与注册顺序相反。 不传递参数。 thesamefunctionmayberegisteredmultipletimes 3360 itiscalledonceforeachregistration .可以多次注册同一函数。 每次注册都会调用。 示例:
# include stdio.h # include stdlib.hvoidf1(void ) {puts('F1 ) ) is working!' ); }voidF2(void ) puts('F2 ) ) is working!' ); }voidF3(void ) puts('F3 ) ) is working!' ); }int main () puts ) ' begin!' );/* atexit (F2 ),而不是调用*/atexit(f1 ),只是将三个函数乘以钩子; //按反序调用atexit(f3 )//何时调用,在运行exit(0)或return 0) 0之前. puts ) ' end!' ); 返回0; }程序的执行结果如下。
写伪代码看看钩子函数有什么用。
当我们写程序时,可能会发生以下情况。 fd1=open (; if(FD10 ) {perror; 退出(1; } fd2=打开(; if(FD20 ) close ) FD1; perror (; 退出(1; (……软盘100=打开); if(FD1000 )关闭) FD1; 关闭(fd2; 关闭(fd3;关闭(fd99 ); perror (; 退出(1; }这很辛苦。 因为有钩子函数,所以可以这么做。 fd1=open (; if(FD10 ) {perror; 退出(1; (}atexit ); -作用为关闭(fd1 ); fd2=open (; if(FD20 ) {perror ); 退出(1; (}atexit ); -作用为关闭(fd2 ); fd100=open (; if(FD1000 ) {perror ); 退出(1; (}atexit ); -角色为关闭(软盘100 );这里举打开文件的例子只是一种情况,我们还可以回收使用malloc开辟的没有释放的空间。
exit和_exit或_Exit exit exit函数在手册的第三章。所以使用man 3 exit来查看。
name exit-causenormalprocesstermination是常规进程终止synopsis # include stdlib.hvoidexit (int status ); descriptiontheexit (功能causesnormalprocessterminationandthevalueofstatus 0377 isreturnedtothepar
ent (see wait(2)).参数 status & 0377 (0377是八进制,换成二进制就是11111111)将被返回给父进程,也就是将status的第八位返回给父进程,第一位是符号位,也就是说status的范围是 -128 — +127。
通常,我们传入参数0,表示正常退出。其他表示非正常退出。
请问下面代码中这个return 0;是给谁看的?
#include <stdio.h>#include <stdlib.h>int main(){printf("Hello.n");return 0;}答:这个return 0;是给当前进程的父进程看的。
我们将该程序通过gcc编译器编译之后,然后通过命令./xxx来执行,从" ./ " 可体现出来当前进程的父进程是shell,是shell将其创建出来的。
使用命令
echo $?我查了下,
该命令的含义可以表示两种,
一种是最后运行的命令的返回值,即执行上一个指令的返回值 。
另外一种是最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
输出结果如下:
0现在,我们将代码进行修改,将return 0;给去掉,再试着执行一下代码。
执行结果仍然是
现在,再试着使用命令
echo $?看(视频教程中)得到的结果:
7我的执行结果是0。
那为什么是7呢?
是因为我们把return 0;这句代码去掉之后,上一条执行的命令就是printf(“Hello.n”);
而printf的返回值就是成功输出字符的个数。
所以当我们使用
echo $?打印出来的结果自然是printf的返回结果。
需要说明:
(视频中输出正常,打印出来的是7,所以返回的是上一个指令的返回值。)
(用我的虚拟机执行我的代码,输出正常,打印出来的是0,所以返回的是最后命令的退出状态。)
exit是库函数。
_exit 或 _Exit是系统调用。
所以,一定是 exit 依赖_exit或_Exit来实现函数功能。
那exit和 _exit或_Exit只是这一点的区别吗?
并不是的。
看下图。
从图中可以看到,在最下面是内核,也就是说当前的程序调用肯定要和内核发生交互(也就是系统调用的实现)。
虚线框是进程的虚拟空间。当用户在用户函数或main函数中或C启动历程中调用_exit或_Exit函数的时候,都是往图中的左侧走的,会直接跳出虚线框,代表的是直接终止。
而当用户在用户函数或main函数中或C启动历程中调用exit函数的时候,不是直接跳出虚线框的,而是先调用n多个终止处理程序(比如钩子函数),之后再调用标准I/O清理程序,然后才依赖于_exit或_Exit来结束当前的进程。
那什么时候应该用exit函数,什么时候又该用_exit或_Exit函数呢?
我们来举个例子来说一下。
int func(){//可以返回0、1、2return 0/1/2;}int main(){int f;f=func();...........switch(f){case 0:case 1:case 2://假设在当前情况下,没有0,1,2这种可能,说明程序出的问题在省略号部分,机器有可能是因为写越界,从而导致该部分将f的值做了修改。default://假设使用exit(1);那么程序就会先调用n多个终止处理程序(比如钩子函数),之后再调用标准I/O清理程序(刷新或同步各种内容),那么可能造成的问题可能就是故障扩大。//那在这种情况下应该怎么做呢?//1、除了应该调用_exit或_Exit函数,直接退出。//2、还可以使用信号abort();将当前进程直接杀死,顺便得到出错的原因。}}