与静态库链接
一:静态链接
《csapp》中的第七章主要讲解了链接,也就是通俗来讲打包的概念,在我们日常的编程作业中我们很有可能不希望让他人知道我们的源代码,这个时候链接就派上了作用,在我们平时编写代码的时候,我们调用的例如:printf,scanf,strcat等函数基本都是来自库文件,作为使用者我们无法知道这些函数真正的源代码,因为这些都是进行了打包操作,封装起来的。
在一个程序的处理过程中,有四个环节最为重要:预处理、编译、汇编、链接:
预处理是将.c文件转换成.i文件
编译是将.i文件转换成.s 文件
汇编是将.s文件转换成.o文件
链接就是把.o文件进行组合传建一个可执行目标文件,在这之后如果我们想运行这个程序,只需要在Linux shell 的命令行上输入它的名字。
在汇编过程中生成了目标文件,目标文件有三种形式:
1.可重定位目标文件(.o):包含二进制代码和数据,其形式可以在编译是与其他可重定位目标文件合并起来,传建一个可执行目标文件。
2.可执行目标文件(a.out):包含二进制代码和数据,其形式可以被直接复制代内存并执行。
3.共享目标文件(.so):一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。
其中,可重定位目标文件是通过命令(例如hello.c):
gcc -c hello hello.c
生成的;
可执行目标文件是通过命令
gcc-o hello hello.c
生成的。
在实现链接之前我们还需要知道符号和符号表的的概念,每个可重定位目标模块都有一个符号表,它包含模块定义和应用的符号信息。在连接器的上下文中,有三种符号:
1.全局符号:由模块定义并能被其他模块引用的全局符号,全局连接器符号对应于非静态的C函数和全局变量。
2外部符号:由其他模块定义并被模块引用的全局符号,对应于在其他模块中定义的非静态C函数和全局变量。
3.局部符号:只被模块定义和引用的局部符号。他们对应于带static属性的C函数和全局变量。这些符号在模块中任何位置都可见,但是不能被其他函数引用。
如果想看一个目标文件中的符号表可以通过下面这条命令:
readelf -s hello.o
符号还有强符号和若符号之分:
强符号:函数和已初始化的全局变量
弱符号:未初始化的全局符号和局部符号。
Linux链接器使用下面的规则来处理多重定义的符号名:
1.不允许有多个同名的强符号。
2.如果一个强符号与多个弱符号同名,那么以强符号为准。
3.如果多个弱符号同名,那么从这些若符号中任意选择一个。
二:实例操作
按照书中的7-6的例子,两个向量例程。每个例程,定义在他自己的目标模块中,对两个向量进行一个向量操作,并把结果存放在一个输出向量中。每个例程有一个副作用,会记录他自己被调用的次数,每次被调用的次数,每次被调用会把一个全局变量加一。
1.向量加法:
/* addvec.c */
/* $begin addvec */
int addcnt = 0;
void addvec(int *x, int *y,
int *z, int n)
{
int i;
addcnt++;
for (i = 0; i < n; i++)
z[i] = x[i] + y[i];
}
/* $end addvec */
2.向量乘法:
/* multvec.c */
/* $begin multvec */
int multcnt = 0;
void multvec(int *x, int *y,
int *z, int n)
{
int i;
multcnt++;
for (i = 0; i < n; i++)
z[i] = x[i] * y[i];
}
/* $end multvec */
要创建这些函数的一个静态库,我们将使用AR工具,如下
linux> gcc -c addvec.c multvec.c
linux> ar rcs libvector.a addvec.o multvec.o
为了使用这个库,我们可以编写一个应用,比如下图中的main2.c,他调用了addvec库例程。包含(或头)文件vector.h定义了libvector中例程函数的函数原型:
/* main2.c */
/* $begin main2 */
#include <stdio.h>
#include "vector.h"
int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];
int main()
{
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
return 0;
}
/* $end main2 */
为了创建这个可执行文件,我i们要编译和链接输入文件main.o和libvector.a:
linux>gcc -c main2.c //生成可重定位目标文件main.o
linux>gcc -static -o prog2c main.o ./libsunse.a //链接
或者等价使用
linux>gcc -c main2.c
linux>gcc -static -o prog2c main2.o -L. -lvector
下图概括了连接器的行为。-static参数告诉编译器驱动程序,链接器应该构建一个完全连接的可执行目标文件,它可以加载到内存并运行,在加载时无需更进一步的链接。-lvector参数是libvector.a的缩写,-L.参数告诉链接器在当前目录下查找libvector.a
当链接器运行时,他判定main2.o引用了addvec.o定义的addvec符号,所以复制addvec.p到可执行文件。因为程序不会引用任何由miltvec.o定义的符号,所以链接器就不会复制这个模块到可执行文件。链接器还会复制libc.a中的printf.o模块,以及许多c运行时系统中的其他模块。
具体执行过程如下:
成功地输出了z向量!