进程总是从 main 函数开始执行的,main函数的函数原型如下:
int main(int argc,char* argv[]);
进程的终止方式分为正常终止和异常终止两种方式,这两种方式的情况如下:
正常终止 1.从 main 函数返回 2.调用 exit 函数 3.调用 _exit 函数 异常终止 1.调用 abort 函数 2.由一个信号终止exit 函数和 _exit 函数用来终止一个程序,它们两个的区别是,调用 _exit 函数会立即进入到内核,而调用 exit 函数,会先进行一些处理善后工作(如刷新IO),然后调用 _exit 函数进入内核。我们可以使用 atexit 函数来注册一些终止处理程序,这些终止处理程序在调用 exit 函数时会自动执行,并且先注册的后执行,后注册的先执行。一个c程序的启动和终止的生命周期图如下:
c 语言的命令行参数,我只前曾经总结过,详情参考我的另一篇博客点击打开链接。
由于历史的原因,c程序的存储空间布局一直是如下的格式:
我们就从下到上一次介绍每一个部分。
正文部分也就是程序体,一般将它设置为只读的,所以程序的正文是可以被几个进程共享的。即使对于经常使用的程序,它的程序正文在内存中也只有一个拷贝。初始化数据段用来存储被赋予初值的全局变量。例如在所有函数之外声明的变量, int max = 100;就存放在这个区域。未初始化数据段用来存储未赋予初值的全局变量。例如,函数之外的声明,long sum[1000];就存储在这个区域中,在这个区域中的值被初始化为0。堆用来为程序在运行时动态的分配内存空间。栈用来存储函数中的一些局部变量以及在发生函数调用时,保存现场信息。最上面用来存放命令行参数和环境变量。
说到环境变量就需要提到进程的环境表。以前 main 函数的原型是下面这个样子的:
int main(int argc,char* argv[],char* envp[]);
#include <stdlib.h> char *getenv(const char *name);
#include <stdlib.h> int putenv(char *string)
#include <stdlib.h> int setenv(const char *name, const char *value, int overwrite); int unsetenv(const char *name);
存储器分配主要涉及到4个函数:
#include <stdlib.h> void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size);
5.setjmp和longjmp
setjmp和longjmp函数用来在各个函数之间来回跳转,相当与是函数之间的goto语句。但是在哥各个函数之间跳转时,需要注意变量是否回滚。一般来说,需要将变量声明为 volatile 为最好,这样就可以保证变量不会因为 longjmp 而发生回滚。volatile 为易失性变量,这样就保证了,变量一直存储在存储器中,从而变量就不会发生变化。
6.共享库
共享库就是把程序可能用到的头文件,放入到内存的一个共享存储区中,而不用放到每一个程序文件中。当程序执行时,再动态的链接共享存储区中的内容。这样就减少了程序正文的长度,但是也相应的加长了程序的执行时间。