进程环境中的[apue]那些事儿都是什么?
摘要:atexit 注册的处理器中可以再调 atexit 或 exit 吗?putenv 或 setenv 增加一个环境变量后 environ 指针地址为什么变了?setjmp & longjmp 跨函数跳转后自动变量为什么回退了
main 函数与进程终止
众所周知,main 函数为 unix like 系统上可执行文件的"入口",然而这个入口并不是指链接器设置的程序起始地址,后者通常是一个启动例程,它从内核取得命令行参数和环境变量值后,为调用 main 函数做好安排。main 函数原型为:
int main (int argc, char *argv[]);
这是 ISO C 和 POSIX.1 指义的,当然还存在下面几种不太标准的 main 原型:
void main (int argc, char *argv[]);
void main (void);
int main (void);
不带 argc & argv 参数的表示不打算接受命令行参数;void 返回值的表示不打算返回一个结束状态。
进程的结束状态码与 main 的返回值关系如下:
main 声明为 int 类型返回值
main 结束前执行了 return x 语句:x
main 结束前执行了无参数 return 语句:未定义 (warning: ‘return’ with no value, in function returning non-void)
main 结束前执行了 exit(x) 函数:x
main 结束前未执行以上语句:未定义 (warning: control reaches end of non-void function)
main 结束前未执行以上语句 [-std=c99]:0
main 声明为 void 类型返回值 (warning: return type of ‘main’ is not ‘int’)
main 结束前执行了 return x 语句:未定义 (warning: ‘return’ with a value, in function returning void)
main 结束前执行了无参数 return 语句:未定义
main 结束前执行了 exit(x) 函数:x
main 结束前未执行以上语句:未定义
测试机为 CentOS 7.9,gcc 版本 4.8.5,每一项的 warning 信息就是基于这两个版本测得。未定义的场景中,均返回 25 这个魔数。
开了 -std=c99 后大部分场景没有改善,仅 main 返回值被声明为 int 类型且在结束前没有调用任何 return 或 exit 时 (第 1 项第 4 小项) 发生了显著变化:从未定义变为返回 0。
进程有 8 种终止方式,其中 5 种为正常终止:
从 main 返回 (无论是否有返回值)
调用 exit
调用 _exit 或 _Exit
最后一个线程从其启动例程返回
最后一个线程调用 pthread_exit
另有 3 种为异常终止:
调用 abort
接到一个信号并终止
最后一个线程对取消请求做出响应
下面重点看一下 3 个 exit 函数:
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
声明差别不大,_exit 与 _Exit 分别是 POSIX.1 与 ISO C 的标准,不过可以将它们视为等价,都直接进入内核。exit 则在它们的基础上做了一些清理工作,主要包含以下几个方面:
清理线程局部存储 (TLS) 信息
按顺序调用注册的终止处理程序
为所有标准 I/O 库打开的流调用 fclose 函数,这会 flush 缓冲的输出数据
关于标准 I/O 库,请参考之前写的这篇文章:《[apue] 标准 I/O 库那些事儿 》。
有了上面的铺垫,可以这样理解可执行程序的启动例程与 main 之间的关系:
...
exit (main (argc, argv));
即 main 的返回值是直接传递给 exit 的 status 参数作为进程结束状态的。
