在多个文件编译之后,每个文件生成了一个.o文件,然后是使用链接器,将.o文件链接为一个可执行文件的。链接器为了创建可执行文件,必须完成两个任务:
1.符号解析:目标文件定义和引用符号。符号解析的目的是将每个符号引用和符号定义联系起来。
2.重定位:汇编器和编译器生成从地址零开始的代码和数据段。链接器通过把每个符号定义与一个存储器位置(虚存)联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器的位置,从而重定向这些段。
这两句话我的理解是1.将不同.o文件中的变量声明和变量定义联系起来,2.因为每个编译出来的.o文件都是从地址零开始的,所以当这些.o文件链接为一个文件是,就需要将地址做相应的调整,保证程序执行的正确性。
举一个例子:
//main.c
void swap();
int buf[2] = {1,2};
int main()
{
swap();
return 0;
}
//swap.c
extern int buf[];
int *bufp0 = &buf[0];
int *bufp1;
void swap()
{
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
上述的小程序是交换数组中的两个数字的位置。
编译的流程图为:
每一个.c文件都会经过编译生成一个.o文件,所有的.o文件经过连接,生成一个可执行文件。
静态连接:
我们使用的标准库,包含了我们经常所用的标准I/O,串操作,整数算术函数等等。下面说下静态连接的必要性:
如果没有静态库,我们将刚才的所有库函数都编译为一个.o文件,当要使用是,我们将这个.o文件连接到我们使用库函数的cpp文件中,例如gcc main.c libc.o. 这样存在的问题,是系统的每一个可执行文件都含有一份标准函数的完全拷贝,而不是我需要哪些函数就有哪些函数。所以静态库应运而生。在链接静态库时,链接器值拷贝被程序引用的目标模块,这样就减少了可执行文件在磁盘和存储器中的大小。
举个例子:
//addvec.c
void addvec(int *x,int *y, int *z, int n)
{
int i ;
for(i = 0; i < n; i ++)
z[i] = x[i] + y[i];
}
//multvec.c
void multvec(int *x, int *y, int *z, int n)
{
int i;
for(i = 0; i < n; i ++ )
z[i] = x[i] * y[i];
}
编译这两个.c文件,创建静态库
gcc -c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o 生成静态库libvector.a其中包括两个.o文件
//main2.c
#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[0]);
return 0;
}
//vector.h
void addvec(int*, int*,int*,int);
void multvec(int*,int*,int*,int);
先编译main.o
gcc -O2 -c main2.c
生成可执行文件
gcc -static -o p2 main2.o ./libvector.a
-static参数告诉编译器,链接器应该构建一个完全连接的可执行目标文件,它可以加载到存储器并运行,在加载时无须更进一步的连接了。当链接器运行时,他判定addvec.o定义的addvec符号是被main.o引用的,所以它拷贝addvec.o到可执行文件。因为程序不引用任何由multvec.o定义的符号,所以链接器就不会拷贝这个模块到可执行文件。链接器还会从libc.a拷贝print.o模块,以及许多C运行时系统中的模块,如图:
最后是动态共享库
我们有了静态库,但是仍然有一些问题:
1静态库的更新,如果静态库需要更新,那么所有依赖于该库的可执行程序都需要重新编译。
2一些常用的函数,例如标准I/O函数print 在运行时,这些函数会被复制到每个运行进程的代码段中去,造成了极大的浪费
所以我们引入了动态库。
程序还是我们在静态库中使用的文件
编译静态库:
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
-fPIC选项只是编译器生成与位置无关的代码,-shared选项指示链接器创建一个共享的目标文件
gcc -o p2 main2.c ./libector.so
这样就创建了一个可执行文件p2.
此时,没有任何libvector.so的代码和数据段被真正的拷贝到可执行文件p2中,取而代之的是,链接器拷贝了一些重定位和符号表的信息。
附:
查看静态库中的函数 nm ****.a
查看动态库所依赖的其他库 ldd ****.so
g++选项
-L:指定链接库的路径,-L. 表示要连接的库在当前目录中 -ltest:指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
例如:g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath