进程终止
exit(int)
执行若干清理活动之后调用 _Exit(int)
返回内核。等价于主函数 return
对应的退出码。使用 atexit(void (*)(void))
注册函数,在退出之后调用。但至多指定 32 个函数。执行完这些用户指定的函数之后,再执行设定好的清理活动,典型的例子可能是 fflush(stdout)
这类事情。
C 程序进程的存储空间
正文段(text):机器指令,一般是只读的。
初始化了的数据段(数据段,data):在 C 的函数外声明初始化后的数据。例如
int maxc = 99;
这样的变量。没有初始化的数据段(block started by symbol,bss):C 函数外声明成
long sum[128];
这等数据。会自动初始化为 0。栈(stack):自动变量储存的位置,包含函数调用信息。每个函数的调用得到一块栈帧,函数结束之后释放栈帧(实际上只是指标移动导致内存可以被重复利用而已)。
堆(heap):用于动态内存分配。
下面是一个典型的内存分布模型(在程序的虚拟内存空间内):
| text | data | bss | heap --> <-- stack | ... |
堆部分从低地址向高地址扩展;栈内存从高地址向低地址扩展。Unix 给出一个 size
程序,以可执行文件为参数,得到它的 bss 、data和 text 段的大小。
共享库
gcc 使用 -static
选项禁止链接动态库,转而全部链接动态库。得到的可执行程序 data 和 text 都会变得巨大无比。
共享库除了减少程序空间,还使得库版本更新(不改变接口的前提下)不必重新编译被依赖的程序。
setjmp
setjmp.h
中的 setjmp(jmp_buf)
和 longjmp(jmp_buf, int)
可以实现栈直接跳转,具体细节参看文档。
习题
很多 Unix 系统中,有意禁止访问数据段(data)的 0 地址处,这是为什么?
C 语音禁止解引用 NULL 指针(因为这是没有意义的)。data 段的 0 禁止访问,则不必手动实现这种禁止。