C程序的启动过程
我们通常认为C语言的起始函数是main函数,实际上一个程序的启动函数不一定是main函数,这个可以采用链接器来设置,但是gcc中默认main函数就是C语言的入口函数,在执行main函数之前,内核会启动一个特殊例程。
这个特殊例程的作用:
- 搜集命令行的参数传递给main函数中的argc和argv;
- 搜集环境信息构建环境表并传递给main函数;
- 登记进程的终止函数;
进程终止函数atexit()
#include <stdlib.h>
int atexit(void(*function)(void));
返回:成功返回0,出错返回-1;
功能:注册终止函数(即main执行结束后调用的函数,当程序通过调用exit()或从main中返回时,参数function指定的函数会先被 调用,然后才真正由exit()结束程序)
- 每个启动的进程都默认登记了一个标准的终止函数;
- 终止函数在进程终止时释放进程所占用的一些资源;
- 登记的多个终止函数执行顺序是以栈的方式执行,先登记的后执行;
进程终止方式
a.正常终止
- 从main函数返回;
- 调用exit(标准c库函数);
- 调用_exit或_Exit(系统调用)
- 调用abort;
- 接收到一个信号并终止;
- 最后一个线程对其取消请求做处理响应;
- 通常程序运行成功返回0,失败返回非0;
- 在shell中可以查看进程返回值(echo $?);
一个问题:main函数退出之后,是否还可以执行程序?
#include
int atexit(void(*func)(void));
其中,atexit的参数是一个地址,当调用此函数时无需传递任何参数,该函数也无返回值,atexit函数称为终止处理程序注册程序,注册完成以后,当函数终止时,exit()函数会主动调用前面的各个函数。由于exit是在main函数调用结束以后调用,所以这些函数的执行肯定在main函数之后。这就是上面问题的答案。即采用atexit函数登记相关的执行函数即可。
简单的示例:
#include <stdlib.h>
#include <stdio.h>
void func1(void)
{
printf("in func1\n");
}
void func2(void)
{
printf("in func2\n");
}
void func3(void)
{
printf("in func3\n");
}
int main(void)
{
atexit(func1);
atexit(func2);
atexit(func3);
printf("I'm main\n");
exit(0);
//return 0;
}
具体执行结果如下所示:
根据exit的执行过程可知,exit首先会调用各个终止处理程序,然后按需多次调用close(),关闭所有打开流,也就是说exit函数会执行一个标准库的清理关闭操作,对所有打开的流调用fclose(),这样就会造成所有缓冲的输出数据都被冲洗写入文件中。
注意:在终止方式方式中,调用_exit,_Exit都不会调用终止程序,异常终止也不会。
如果将上述程序中最后的exit(0)改为_exit(0)或_Exit(0),执行结果如下:
也就是说根本不会执行到最开始注册的函数。