一.main函数是如何被调用的
内核执行C程序时,在调用main函数前先会调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用 main函数做好安排。
二.进程的八种终止方式
1. 从main返回
2. 调用exit
3. 调用_exit或_Exit
4. 最后一个线程从其启动例程返回
5. 最后一个线程调用pthread_exit
异常终止有三种方式,它们是:
1. 调用abort
2. 接到一个信号并终止
3. 最后一个线程对取消请求做出响应
上一节提到到的启动例程会使main返回后立即调用exit函数,如果启动例程以C代码形式表示(事实上启动例程是常常使用汇编语言编写),那么它调用main函数的形式就可能是如下:exit(main(argc,argv));
而来看exit函数,
#include<stdlib.h>
void exit(int status); void _Exit(int status);
#include<unistd.h>
void _exit(int status);
三个函数用于正常终止一个程序,其中_exit和_Exit马上进入内核,exit则先执行一些例如调用执行各终止处理程序,关闭所有标准IO流(为所有打开流调用fclose函数)等清理操作然后才进入内核。
三.atexit函数
一个进程可以登记多达32个函数由exit函数自动调用。我们称这些函数为终止处理程序,并调用atexit函数来登记这些函数。
#include<stdlib.h>
int atexit(void (*func)void)
参数是一个函数地址,且调用此函数时无需向它传送任何参数,也不期望返回一个值。调用这些函数的顺序与登记顺序相反。且同一函数登记多次也会被调用多次。
注:exit执行时,首选调用各终止程序,然后再调用fclose,关闭所有打开流,如果程序调用了exec函数族中任一函数,则将清除所有已安装的终止处理程序。
四.环境表
每个程序都会接收到一张环境表,与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的c字符串的地址。全局变量environ包含了该指针数组的地址
extern char **environ;
因为ISO C和POSIX.1都规定不使用第三个参数,通常用getenv和putenv函数来访问特定的环境变量,而不是environ变量,当然要查看整个环境还是必须使用environ指针。
五.C程序的存储空间布局
正文段(or 代码段),数据段,BSS(Block Started by Symbol,来源于早期汇编运算符)非初始化数据段,栈,堆。。如图
其中,正文段:由CPU执行的机器指令部份,通常是正文段可以共享的且通常是只读的(显然,防止程序以外而修改自身指令)。
数据段:包含了程序中需明确地赋初值的变量。
BSS段:程序开始前,内核将此段中的数据初始化为0或空指针。如以下声明:long sum[100];将会被放于非初始化数据段。
另外,未初始化数据段内容并不放在磁盘上的程序文件中,需要存放的只有正文段和初始化数据段。
六.setjmp ,longjmp
这两个函数主要有几点需要注意
可以跨越函数,对于处理发生在深层嵌套函数调用中的出错情况是非常有用的,它在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include<setjmp.h>
int setjmp(jmp_env);
void longjmp(jmp_buf env,int val);
其中jmp_env是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。当检查到一个错误时,调用longjmp函数,其中第二个参数是非零值,它将成为从setjmp处返回的值。因为对于一个setjmp可以有多个longjmp,可以根据返回值判断造成返回的longjmp是在哪个函数中。
而在调用longjmp后返回到main函数时存放在存储器中的变量将具有longjmp时的值,而在cpu和浮点寄存器中的变量则恢复为调用setjmp时的值。如果以不带编译器优化对程序进行编译,所有变量都存放在存储器中(会忽略register存储类说明),所有变量都不会回滚。而进行了优化后,自动变量和register变量都存放在寄存器中,volatile变量仍然会存放在存储器中。如果要编写一个使用非局部跳转的可移植程序,则必须使用volatile属性。