一、5种正常终止
1.从main函数返回
int main()
{
printf("Hello World!\n");
return 0;
}
进程的返回值是给其父进程看的。
如我们这里,父进程就是shell,程序返回的0就是由shell接收的。
在命令行输入:
echo $?
这条shell命令是打印上一条指令的执行状态,输出如下:
0
可以看到,父进程shell看到子进程的退出状态就是返回值0。
下面我们将return 0语句注释掉,如下:
int main()
{
printf("Hello World!\n");
//return 0;
}
这里我们再执行一下这个程序,再用shell命令行查下返回值,可以看到输出:
13
因为字符串"Hello World!\n",就占用了13个字符空间,printf函数的返回值就是返回的输出字符数目,即13。
2.调用exit
基本调用方法就是将exit(0)替换掉上文的return 0,如下:
int main()
{
......
exit(0);
}
exit的函数声明是:
void exit(int status);
执行exit函数会导致进程正常地终止,并且会将 status & 0377 这个值返回给父进程。
0377是8进制,把它转换成二进制即为:
011,111,111
总共有8个1,status和低位8个1相与,意味着这个数值就是保留着低8位数值。而status是有符号型整数,就意味着8位数值能表示的范围是-127~128,即
exit能带回去的环境为256种,值范围是-127~128.
此外,exit()退出时会执行退出处理程序,如atexit()和on_exit()。两个函数大同小异,我们只举一个例子。
atexit():钩子函数
钩子函数的作用就相当于一些面向对象语言的析构函数。
atexit的函数声明如下:
int atexit(void (*function)(void));
钩子函数就类似注册函数,传入的参数是一个函数指针,函数指针的函数签名必须是void参数和void返回值。
有示例代码如下:
#include <stdio.h>
#include <stdlib.h>
static void f1(void)
{
puts("f1 is working!");
}
static void f2(void)
{
puts("f2 is working!");
}
static void f3(void)
{
puts("f3 is working!");
}
int main()
{
puts("Begin!");
atexit(f1); // 只是把函数f1、f2、f3挂到钩子上,还未被调用
atexit(f2);
atexit(f3);
puts("End!");
// 在执行exit时,才会逆序调用挂在钩子上的函数
exit(0);
}
输出如下:
[root@localhost fs]# ./atexit
Begin!
End!
f3 is working!
f2 is working!
f1 is working!
可以看到如我们所料,f1、f2和f3这三个函数,调用顺序和注册顺序是相反的。
那么钩子函数到底有什么作用呢?
钩子函数的作用就相当于go语言中的defer语句。
试想一下这样的情景,我们要依次open 100个文件,最后也需要在主进程末尾 close100次,这就显得很不美观,而且很容易漏掉。这个时候最好的方法就是open一个,挂钩子一个,如下:
fd1 = open("/tmp/file1", O_RDONLY);
atexit(); // --->close(fd1)
fd2 = open("/tmp/file2", O_RDONLY);
atexit(); // --->close(fd2)
...
3.调用_exit或_Exit
首先要明确一点:
exit是库函数,_exit和_Exit是系统调用。
_exit的函数声明:
#include <unistd.h>
void _exit(int status);
_Exit的函数声明:
#include <stdlib.h>
void _Exit(int status);
这两个函数相比exit()函数的区别在于:
_exit()和_Exit()是不执行任何钩子函数程序和标准I/O清理程序的。eixt()函数会先调用钩子函数atexit(),再刷新stdio流缓冲区,最后由status提供的值执行_exit()系统调用。
有时候为了避免故障扩大,不想要exit的钩子调用和I/O清理,就可以直接调用_exit()函数。
网友评论