编译器做了些什么
图1:编译器通常分割成几个更小的程序
静态链接与动态链接
图2:静态链接与动态链接的区别
动态链接的优势:
1, 动态链接可执行文件比功能相同的静态链接的可执行文件的体积小。它能够节省磁盘空间和虚拟内存,因为函数库只有在需要时才被映射到进程中。以前,避免把函数库的拷贝绑定到每个可执行程序文件的惟一方法就是把服务器置于内核而不是函数库中,使得内核膨胀。
2, 所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。操作系统内核保证映射到内存中的含数据库可以被使用它的其他进程共享,提供了更好的I/O和交换空间利用率,节省了物理内存,从而提高了系统的整体性能。如果可执行文件是静态链接的,每个文件都将拥有一份函数库的拷贝,显然极为浪费空间。
关于函数链接
1, 动态库文件的扩展名是.so,而静态库文件的扩展名是.a,并以libname的形式命名。
2, gcc通过-lthread选项,告诉编译器链接libthread.so。
3, 编译器期望在确定的目录寻找库。默认查找一些特定目录如/usr/lib, /lib,-Lpathname告诉编译器在其他位置查找库。
4, 通过观察头文件,确认使用的函数库。
如math.h-->/usr/lib/libm.so, thread.h—>/usr/lib/libthread.so
运行时数据结构
图3:源文件到可执行文件的映射
图4:可执行文件内核映像
图5:共享库的虚拟地址空间
图6: 过程活动记录
C语言自动提供的服务之一就是跟踪调用链,即哪些函数调用了哪些函数,当下一个return语句执行后,控制将返回何处等。C语言通过堆栈中的过程活动记录解决这一问题,当每个函数被调用时,都会产生一个过程活动记录(或类似的结构)。过程活动记录是一种数据结构,用于支持过程调用,并记录调用结束后返回调用点所需要的全部信息,如图6所示。活动记录内容的描述很具有说明性,结构的具体细节在不同的编译器中各不相同。过程活动记录并不一定在堆栈中,有时为了提高效率,将过程活动记录置于寄存器中。
从堆栈实现函数调用的方式可以解释为什么不能从函数中返回一个指向该函数局部自动变量的指针。
setjmp 和 longjmp
setjmp和longjmp通过操纵过程活动记录实现的,部分弥补了C语言有限的转移能力,这两个函数必须协同工作。
1, setjmp(jmp_buf j)必须首先调用。它表示“使用j记录现在的位置。函数返回零。”
2, longjmp(jmp_buf j, int i)可以接着被调用。它表示“回到j所记录的位置,让它看上去像是从原先的setjmp函数返回的一样。但是函数返回i,使代码能够知道它是实际上时通过longjmp返回的。
3, 当使用于longjmp()时,j的内容被销毁。
semjmp保存一份程序的计数器和当前栈顶指针,也可以根据需要保存一些初始值。longjmp恢复这些值,有效的转移控制并把状态重置回到保存状态的时候,即“展开堆栈”。
与goto的区别:
1, goto语句不能跳出C语言当前函数,而longjmp可以跳得更“远”。
2, longjmp只能跳回曾经到过的地方,在这个“到过的地方”执行setjmp记录当时的状态信息。
longjmp接受一个额外的整型参数并返回它的值,这可以知道是由longjmp转移到这里的还是从上一条语句执行后自然而然的到这里的。
#include<stdio.h>
#include <setjmp.h>
jmp_buf ebuf;
void f2(void);
int main(void)
{
int i;
printf("1");
i=setjmp(ebuf);
if(i==0)
{
f2();
printf("This will not be printed.");
}
printf("%d",i);
getchar();
return 0;
}
void f2(void)
{
printf("2");