一. main函数
(1)c程序总是从main函数开始执行,main函数原型:int main(int argc,char *argv[]);----------argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组
(2)在内核执行c程序时(使用exec函数),在调用main前先调用一个特殊的启动例程。启动例程从内核取得命令行参数和环境变量值,然后为调用main函数做好准备。
二. 进程终止
1. 进程终止方式
5种为正常终止:
1)从main返回;
2)调用exit;
3)调用exit或Exit;
4)最后一个线程从其启动例程返回;
5)最后一个线程调用pthread_exit
异常终止3种方式:
1)调用abord;
2)接到一个信号并终止;
3)最后一个线程对取消请求做出响应
2. exit函数
有三个函数用于正常终止一个程序:_exit和 _Exit立即进入内核,exit则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准I/O流等),然后进入内核。
void exit(int status);
void _Exit(int status);//exit和_Exit是ISO C说明的
void -exit(int status);//_exit是POSIX.1说明
三个exit函数都带一个整型参数,称为终止状态。1)这些函数不带终止状态,2)main执行一个无返回值的return,3)main没有声明返回类型为整型,这三种状态下进程的终止状态是未定义的。未定义时一般情况下默认终止状态为0
3. atexit函数
一个进程有多个终止处理程序,由exit函数自动调用,并调用atexit函数来登记这些函数。exit调用这些函数的顺序与它们登记时候的顺序相反
int atexit(void(*func)(void));//若成功返回0,出错返回非0
/*例程:登记终止处理程序 */
static void exit_1(void);
static void exit_2(void);
int main() {
if(atexit(exit_1) != 0)//记录exit_1的终止处理程序
err_sys("can't register exit_1");
if(atexit(exit_2) != 0)
err_sys("can't register exit_2");
if(atexit(exit_2) != 0)
err_sys("can't register exit_2");
return(0); }
static void exit_1(void) {
printf("handle exit_1\n"); }
static void exit_2(void) {
printf("handle exit_2\n"); }
输出结果:
分析:第二个终止处理程序被登记两次,所以也会被调用两次。
三. 命令行参数
当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。
int main(int argc,char *argv[]) {
int i;
for(i=1;i<argc;i++)
{
printf("argv[%d]:%s\n",i,argv[i]);
}
exit(0); }
输出结果:
四. 环境表
在历史上,大多数UNIX系统支持main函数带有三个参数,其中第三个参数是环境表的地址:
int main(int argc,char *argv[],char *envp[]);
现在基本没有用到environ指针,main函数一般都只有两个参数,通常用getenv和putenv函数来访问特定的环境变量,而不是用environ变量。
五. C程序的存储空间布局
C程序一直由下列几部分组成:
1) 正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器,C编译器和shell等)在存储器中也需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其指令。
2) 初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中任何函数之外的声明,使变量的初值放在初始化数据段中。
int max = 43;
3) 非初始化数据段。通常将此段称为bss段,这一名称来源于早期汇编程序一个操作符,意思是“由符号开始的块”(block started by symbol),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。函数外的声明,使变量存放在非初始化数据段中。
long sum[100];
4) 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时分配存储空间。通过以这种方式使用栈,C递归函数可以工作。递归函数每次调用自身时,就用一个新的栈帧,因此一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。
5) 堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。
对于32位Intel x86处理器上的Linux,正文段从0x08048000单元开始,栈底则在0xC0000000之下开始(在这种特定结构中,栈从高地址向低地址方向增长)。堆顶和栈顶之间的未用虚地址空间很大。
未初始化的数据段的内容不存放在磁盘程序文件中。其原因是,内核在程序开始运行前将它们都设置为0。需要存放在磁盘程序文件中的段只有正文段和初始化数据段。
六. 存储器分配
ISO C说明 了三个用于存储空间动态分配的函数
1)malloc 分配指定字节数的存储区。此存储区中的初始值不确定
2)calloc 为指定数量具指定长度的对象分配存储空间,该空间的每一位都初始化为0
3)realloc 更改以前分配区的长度(增加或减少)
void *malloc(size_t size);
void *calloc(size_t nobj,xize_t size);
void *realloc(void *ptr,size_t newsize);//返回值:成功返回非空指针,出错返回NULL
void free(void *ptr);
注意:释放一个已经释放了的块,调用free时所用的指针不是三个alloc函数的返回值,如若一个进程调用malloc
函数,但却忘记调用free函数,那么该进程占用的存储器就会连续增加,这被称为泄露。
七. 环境变量
环境字符串的形式通常如下:
name=value
ISO C定义了一个函数getenv,可以用其取环境变量值,但该标准又称环境的内容是由实现定义的。
char *getenv(const char *name);//指向与name关联的value的指针,未找到返回NULL
此函数返回一个指针,他指向name=value字符串中的value。我们应当使用getenv从环境中取一个指定环境变量的值,而不是直接访问environ
int putenv(char *str);//成功返回0,错误返回非0
int setenv(const char *name,const *value,int rewrite);//成功返回0,错误返回-1
int unsetenv(const char *name);//成功返回0,错误返回-1
1)putenv取形式为name=value的字符串,将其放到环境表中,如果name已经存在,则先删除其原来的定义。
2)setenv将name设置为value。如果在环境中name已经存在,那么(a)若rewrite非0,则先删除其现有的定义,(b)若rewrite为0,则不删除其现有定义。
3)unsetenv删除name的定义。
八. setjmp和longjmp函数
c中,goto语句不能跨越函数,执行这类跳转功能的是函数setjmp和longjmp。用于处理发生在深层嵌套函数调用中的出错情况。在栈上跳过若干调用帧,返回到当前调用路径的某个函数中。
int setjmp(jmp_buf env);//返回值:若直接调用则返回0,若从longjmp调用返回则返回非0值的longjmp中的val值 /*jmp_buf为特殊类型,是某种形式的数组,存放在调用longjmp时能用来恢复栈状态的所有信息*/
void longjmp(jmp_buf env,int val);//调用此函数则返回到语句setjmp所在的地方,其中env 就是setjmp中的 env,而val 则是使setjmp的返回值变为val