一、进程的终止
首先我们需要知道一个进程什么情况是正常终止什么情况是异常终止。
正常终止:
从main函数返回
调用exit
调用_exit或_Exit
最后一个线程从其启动例程返回
最后一个线程调用pthread_exit
异常终止:
调用abort
接到一个信号并终止
最后一个线程对其取消请求做出相应
1.atexit()
该函数用于注册一系列函数,在程序正常终止(除_exit和_Exit)时按注册顺序逆序调用这些函数。类似于析构函数。因为该函数是逆序调用挂载(注册)的函数,好像挂钩子一样先挂后取,所以也叫钩子函数。
参数为要注册的函数,返回0表示成功。
钩子函数实际应用时可以更方便地释放资源,因此十分重要。以下是一个例子。
当打开的fd较多时,每次异常判断都需要把前面的fd关闭,这会很麻烦,而使用钩子函数可以很好的解决。
2.exit()和_exit(),_Exit()的区别
exit()和_exit()都属于正常终止,它们之间有以下区别:
- exit()函数是标准C库中的函数,而_exit()函数是系统调用。
- exit()函数会先执行一些清理工作,例如调用注册的终止处理函数(通过atexit()注册的函数),然后关闭所有打开的文件描述符,并刷新缓冲区。最后,它会向操作系统发送一个终止信号,告知操作系统进程已经正常终止。
- _exit()函数直接终止进程,不会执行任何清理工作,也不会关闭文件描述符或刷新缓冲区。它会立即返回到操作系统,并且不会发送终止信号。因此,使用_exit()函数可能会导致一些资源泄漏或未完成的操作。
- exit()依赖于_exit()
二、命令行分析
在主函数的参数中,有许多参数是作为选项传进去的,如ls --all -l,这些命令行参数的分析可以用getopt()或getopt_long()来实现。这里只讲getopt().
1.getopt()
该函数用来分析argc中的选项(以-或--开头)及其参数。
参数argc是命令行参数的数量,argv是一个指向命令行参数字符串数组的指针,optstring是一个包含选项字符的字符串。
getopt函数会依次解析命令行参数,并返回解析出的选项字符。如果所有选项都已解析完毕,则返回-1。如果解析到optstring中没有的选项,则返回'?'。在每次调用getopt函数时,它会更新全局变量optarg和optind。
- optarg是一个指向当前选项参数的指针。如果选项有参数,则optarg指向该参数的字符串;如果选项没有参数,则optarg为NULL。
- optind是下一个要解析的命令行参数的索引。
如果optstring中某个字符后面是':',则说明该字符需要一个参数。如果是'::',说明参数是可选的。如果optstring第一个字符是'-',则argv中不是选项的元素会被当作选项字符为1的参数。
以下是一个使用示例:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int
main(int argc, char *argv[])
{
int flags, opt;
int nsecs, tfnd;
nsecs = 0;
tfnd = 0;
flags = 0;
while ((opt = getopt(argc, argv, "nt:")) != -1) {
switch (opt) {
case 'n':
flags = 1;
break;
case 't':
nsecs = atoi(optarg);
tfnd = 1;
break;
default: /* '?' */
fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
argv[0]);
exit(EXIT_FAILURE);
}
}
printf("flags=%d; tfnd=%d; optind=%d\n", flags, tfnd, optind);
if (optind >= argc) {
fprintf(stderr, "Expected argument after options\n");
exit(EXIT_FAILURE);
}
printf("name argument = %s\n", argv[optind]);
/* Other code omitted */
exit(EXIT_SUCCESS);
}
三、环境变量
每个程序都会接收一个环境表,它和argv一样是一个字符串数组char *environ[],里面存放的是环境变量(name = value),在C语言程序中可以通过extern char **environ来声明并访问其内容。
C语言中有与环境变量相关的库函数,这里介绍一下getenv(),setenv(),unsetenv(),putenv().
1.getenv()
该函数用于获取指定的环境变量。
参数为要获取的环境变量的name.
获取PATH的value:
puts(getenv("PATH"));
2.setenv(),unsetenv()
setenv()用于添加(若不存在)或覆盖(若overwrite参数不为0)一个环境变量,unsetenv()用于删除(若存在)一个环境变量。
返回0为成功,否则返回-1.
3.putenv()
该函数也是用于添加或修改环境变量的,其参数的形式是"name=value",但它的参数没有const修饰,所以不建议使用。
四、函数setjmp和longjmp
在C语言中,goto是不能跨函数跳转的,而执行这种类型跳转功能的是函数setjmp和longjmp。这两个函数对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的。C++中的try-catch语句块也可实现该功能。
1.setjmp()
该函数用于设置跳转点。
参数为jmp_buf类型,可以理解为跳转点标志,因为在longjmp中也要用到该标志,所以通常定义为全局变量。
返回值为0时表示设置成功,为非0时表示跳转成功。
2.longjmp()
该函数用于跳转到指定跳转点。
第一个参数为目标跳转点的标志,第二个为跳转后setjmp()的返回值,如果为0会被替换为1。
3.使用实例
下面的例子中main调用A,A调用B,正常的将逐层返回,而用setjmp和longjmp可以直接从B跳回main.
正常情况:
#include <setjmp.h>
#include <iostream>
using namespace std;
void B()
{
cout<<"B()begin"<<endl;
cout<<"B()end"<<endl;
}
void A()
{
cout<<"A()begin"<<endl;
cout<<"call B()"<<endl;
B();
cout<<"A()end"<<endl;
}
int main()
{
cout<<"main()begin"<<endl;
cout<<"call A()"<<endl;
A();
cout<<"main()end"<<endl;
return 0;
}
跳转:
#include <setjmp.h>
#include <iostream>
using namespace std;
jmp_buf save;
void B()
{
cout<<"B()begin"<<endl;
longjmp(save,3);
cout<<"B()end"<<endl;
}
void A()
{
cout<<"A()begin"<<endl;
cout<<"call B()"<<endl;
B();
cout<<"A()end"<<endl;
}
int main()
{
cout<<"main()begin"<<endl;
cout<<"call A()"<<endl;
int flag = setjmp(save);
if(flag == 0)
A();
else
cout<<"jumped here,retval is "<<flag<<endl;
cout<<"main()end"<<endl;
return 0;
}