链接过程分类两种,动态链接和静态链接。如果函数库的一份拷贝是可执行文件的物理组成部分,则为静态链接,动态链接生成的可执行文件只包含了文件名,在运行时,程序会自动寻找所需要的函数库。函数只有在运行的时候才会被解析调用,它是在main函数之前就决定好了。
静态链接与动态链接的优缺点就很明显了。静态链接提前把需要的函数都载入到了可执行文件中,所有更加独立,速度更快。缺点就是程序更新不方便,生成的可执行文件更大。动态链接有点更多了,它可以随时更新自己的功能,而不需要重新编译,生成的文件更小。
动态链接被发明的原因之一是为了把程序和特定的函数库版本分离,也就是标准接口固定,后续的函数库迭代不受版本的影响。
(1).相同条件下,可执行文件更小。因为动态链接只有在需要的时候,才把对应的函数和数据载入到进程。
(2).所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。这句话有点拗口,简单来讲,就是共享库的函数只占据一份空间,不会再每一个需要的地方都占用一份,提高了空间利用率。
(3).动态链接允许用户在运行时选择需要执行的库函数版本。动态链接使得函数库的版本升级更为容易,新的函数版本可以随时发布,只要安装到操作系统中,旧的程序就能自动获得新版本函数库的优点而不需要重新链接。
动态链接会关注函数所在的位置,所有不要改动原本动态函数所在的位置,比如C语言的动态库一般都在libc中。当然,我们也可以自己决定位置,需要在链接的时候说明。
静态库被称为archive,通过ar(一种生产工具)来创建和更新。静态库约定在它们的文件名中使用.a的扩展名。
还有一种技术,介于静态和动态之间,它是静态共享库,在生命周期内,它们的地址始终固定,可以直接绑定到操作系统中,但是由于限制颇多,需要专门的操作系统支持,这里只提及一句。
动态链接库由链接编辑器ld创建,动态链接库的文件扩展名为.so(shared object)。
回忆一下编译的四个阶段,预处理->编译->汇编->链接
(1).预处理:替换掉预处理命令,生成.i的文件
命令:gcc -E 文件名 -o 指定生成文件名
(2).编译:生成汇编文件,生成.s的文件
命令:gcc -S 文件名.i -o 指定生成的文件名
(3).汇编:生成二进制文件,生成.o的文件
命令:gcc -c 文件名 -o 指定生成的文件名
(4).链接:生成可执行文件,Linux中生成的默认文件名叫a.out,在window中会生成.exe
命令: gcc 文件名 -o 指定生成的文件名
动态链接生成的文件名叫.so或者.dll(windows系统),而静态文件的扩展名是.a
假设我们有如下几个文件:main.c test.h test.c
显然main.c是主函数,test.c是集成了许多函数的功能函数,让函数在运行期间调用test.c,编译过程如下:
1.gcc test.c -shared -o test.dll
生成test.dll动态文件,也可以取名test.so
2.gcc main.c test.dll -o test.exe
生成test.exe可执行文件。
知道这两个步骤就差不多了,需要注意的是,可以通过-L来指定文件路径。
使用size命令,查看a.out文件,它会给出三种类型数据的大小,分别是text,data,bss
data段(数据段):存放初始化后的全局以及静态变量
text段(文本段):可执行文件的指定,比如加减乘除,调用函数等等
bss段:保存没有值的变量。
局部变量不进入a.out,它们是在运行时创建的
举个例子
- char str[100];
- static double a;
-
- int b = 10;
- static double pi = 3.14;
-
- int main()
- {
-
- int i = 3, j, *ptr;
-
- ptr = malloc(100*sizeof(int));
- ptr[3] = 3;
- return 0;
- }
char str[100];
static double a;放在bss中
int b = 10;
static double pi = 3.14;放在data中
int i = 3, j, *ptr;
在运行的时候自动创建,并没有保存在a.out中
ptr = malloc(100*sizeof(int));
ptr[3] = 3;这种指令放在text中
补充:C语言运行时,操作系统对a.out的操作
主要是使用mmap函数,将a.out的各个段映射到进程空间之中,每个段都有不同的权限,有的是可读可执行,有的是可读可写,操作系统还会根据情况分配一些栈空间和堆空间,给临时变量或者malloc这样的函数使用,bss和data其实被映射到了一起,都叫数据段,