C程序的内存布局
首先看下面这个图,有个大致的概念:
每一个C程序都有自己独立的地址空间,它们包含五个部分:
1. 代码段(Text segment)
存放程序的执行代码,即使在多进程的环境下,它也只有一份。
2. 初始化数据段(Initialized data segment)
例如初始化的全局变量
int maxcount = 99;
3. 未初始化的数据段(Uninitialized data segment)
例如初始化的全局变量,这些变量的值将由kernel初始化为0
long sum[1000];
4. 栈(stack)
在程序的执行过程中存储函数调用信息的动态数据结构。
5. 堆(heap)
堆用于动态分配和释放程序所使用的对象。
需要注意的是,这是在内存中的布局,事实上,只有代码段和初始化数据段的内容才需要被真正存储在程序文件中。
环境表(Environment List)
在unix中,每一个进程都有自己的环境变量的集合,它们被称之为环境变量时因为表中的默认变量常常被用于配置目的。在前面的文章段落中,我们讨论了C程序的内存布局,可以看见环境表通常位于进程地址空间的高地址处,正好在栈的上面。让我们详细看一下环境表的结构:
我们通常会有一个全局指针指向该表的首地址,它的声明如下:
extern char **environ;
可以看出该表实际上是一个指针数组,每一个元素指向了一个环境变量的键值对字符串,最后一个元素后面跟着一个空指针用于标识表的结束。一般来说,默认的环境变量都是以全大写形式的。如非必要,我们不应该自己通过environ指针去访问该表,POSIX自会为我们提供合适的函数去访问它们:
char *getenv(const char *name);
int putenv(char *str);
int setenv(const char *name, const char *value,
int rewrite);
int unsetenv(const char *name);
进程的中止方式
进程的正常中止方式有以下5种:
1. 调用exit库函数。
该函数通常在正常关闭前会做些清扫工作(例如关闭打开的流),之后再返回kernel。
1. 通过main函数的返回值。
实际上,其内在的实现方式相当于:exit(main(argc, argv));
3. 调用 _exit函数 或 _Exit函数。
该种方式直接返回kernel,不做任何的清扫工作。
只不过前者属于系统调用,后者属于库函数。
4. 进程的最后一个线程结束返回。
5. 进程的最后一个线程调用 pthread_exit函数。
以下是进程的非正常中止方式:
6. 调用abort函数。
7. 接收到一个信号。
8. 对最后一个线程的cancellation request进行响应。
有时候,我们希望在进程结束时,能够做一些额外的操作,例如资源回收等,我们可以通过atexit函数进行注册,其声明如下:
int atexit(void (*func)(void));
每个进程可以利用这个函数注册至少32个函数(根据实现不同而不同),这些被注册的函数将被exit函数自动调用。
exit 函数会首先调用这些注册了的函数,然后关闭所有打开了的流,最后才返回kernel。
如果我们调用了exec系列的函数,那么,任何注册了的结束处理函数都将被清除。