main函数
我们先分析一下当一个C程序开始执行时,main函数是如何被调用的。C程序总是从main函数开始执行,java、C++、C#等这些类C语言的程序也是。(这里是我的猜测,以后需要求证)
main函数的原型:
int main (int argc, char* argv[]);
其中,argc是命令行参数的数目,argv是指向命令行参数的指针所组成的数组。当内核执行C程序时(其实使用一个exec函数)在调用main前,会先调用一个启动程序。
C编译器在连接时调用连接编辑器,连接编辑器将这个特殊的启动程序指定为用户可执行程序的起始地址。
这个特殊的启动程序从内核取得命令行参数和环境变量 (每一个程序都会接收到一张环境表后续我们会单独讲环境变量),为调用main函数做好准备。
进程终止
其实结束进程的方式有很多,总共有8种方式使进程终止。
5种正常:
1 . 从 main 函数返回。
2 . 调用 exit();
3 . 调用 _exit 或者 _Exit
4 . 最后一个线程从其启动例程返回
5 . 最后一个线程调用 pthread_exit
3种异常
1 . 调用abort();
2 . 接收到一个信号
3 . 最后一个线程对取消请求作出响应。
启动程序通常是用汇编语言编写的,这里我们就用C语言表示一下从main函数返回后立即调用exit函数
exit( main( argc , argv) )
退出函数
_exit 、_Exit立即进入内核态,exit 则先执行以下资源清理工作,然后返回内核态
#include <stdlib.h> //由ISO C说明
void exit(int status);
void _Exit(int status);
#include <unistd.h> //由POSIX.1说明
void _exit(int status);
exit函数总是执行一个标准I/O库的清理关闭操作对所有打开流调用fclose函数。这将使输出缓存中的所有数据都被冲洗(写到文件中)。
这3个退出函数都带一个整形参数,称为止状态。大多数unix系统的shell都是提供检查程序终止状态的方法。
echo $?
以下三种情况下进程返回的终止状态是未定义的:
1 . 调用这些函数时不带终止状态。
2 . main函数执行了一个无返回值的return语句,
3 . main函数没有声明返回值类型为整形
但是,如果main的返回类型是整形,并且main执行到最后一条语句时返回;那么该进程的终止状态是0.
main函数中exit(0),是与return 0;等价的。
函数atexit
一个进程可以登记/注册32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序
程序可以用atexit函数来注册这些函数。
#include <stdlib.h>
int atexit(void (*fun)(void));
atexit的参数是一个函数地址,当调用此函数地址指向的函数时,指向的函数是不需要传入任何参数的,并且也不期望放回值。
在执行到exit函数时,这些注册的函数的调用顺序刚好是与注册顺序相反的,(其实就是一个栈)同时一个函数如果被atexit注册多册,那么结束时也就会被调用多次。
例子:
#include <stdio.h>
#include <stdlib.h>
static void exit_1(void);
static void exit_2(void);
int main(void){
if( atexit(exit_2) !=0 ){
printf("can't register exit_2");
}
if( atexit(exit_1) !=0 ){
printf("can't register exit_1");
}
if( atexit(exit_1) !=0 ){
printf("can't register exit_1");
}
printf("main is done!\n");
exit(0);
}
static void exit_1(void){
printf("first exit handler\n");
}
static void exit_2(void){
printf("second exit handler\n");
}
------------------------------------------------------------------------------------------------
./a.out
main is done
first exit handler
first exit handler
second exit handler
根据ISO C和 POSIX.1,exit首先调用各终止程序,然后关闭所有打开的I/O流。POSIX.1扩展了ISO C标准,它说明,如果程序调用exec函数 族中的任一函数,则将清除所有已安装的终止处理程序。上图显示了一个C程序时如何启动以及它终止的各种方式。
注意:
内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式(通过调用exit)调用_exit 或者 _Exit。
当然,进程也可以通过一个信号来终止它,但是这是一种强制行为。
命令行参数
当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序,这是Unix shell的一部分常规操作。
环境表
记住,每个程序它都会接收到一张环境表。和参数表一样,环境表也是一个字符指针数组。其中每个指针都指向一个以null结束的C字符串的地址。
全局变量environ是指向这个字符指针数组的指针,也就是说,environ中存放的是环境表的地址。
extern char** environ;
看一个例子:
环境变量的格式: name = value 这样的字符串组成
通常,如果我们想在程序中使用环境变量,可以通过getenv获取对应环境变量的值,也就是value。或者通过putenv设置环境变量。如果想查看整个环境,则必须通过使用environ这个指针。
参考 Advanced Programming in the Unix Environment (Third Edition)