静态库
编译系统在编译的过程中,将所有相关的目标模块打包成一个单独的文件,称为静态库(static library),它可以用做链接器的输入。当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
在Unix系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。存档文件名由后缀.a
标识。为了使我们对库的讨论更加形象具体,假设我们想在一个叫做libvector.a
的静态库中提供如下的向量例程:
/* file name: 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];
}
}
/* file name: 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];
}
}
为了创建该库,我们使用 AR
工具:
unix> gcc -c addvec.c multvec.c
unix> ar rcs libvector.a addvec.o multvec.o
为了使用这个库,我们可以编写一个应用( main.c
),它调用 addvec
库例程。(包含头文件 vector.h
定义了 libvector.a
中例程的函数原型)。
/* file name: main.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[1]);
return 0;
}
/* file name: vector.h */
void addvec(int *x, int *y,
int *z, int n);
void multvec(int *x, int *y,
int *z, int n);
为了创建这个可执行文件,我们要编译和链接输入文件main.o
和libvector.a
:
unix> gcc -O2 -c main.c
unix> gcc -static -o p2 main.o ./libvector.a
下图概括了链接器的行为。
动态库
静态库解决了许多关于如何让大量相关函数对应用程序可用的问题。然而,静态库仍然有一些明显的缺点。静态库和所有的软件一样,需要定期维护和更新。另一个问题是几乎每个C程序都使用标准I/O函数,如printf
和 scanf
。在运行时,这些函数的代码会被复制到每个运行进程的文本段中。在一个运行50-100个进程的典型系统上,这将是对稀缺的存储器系统资源的极大浪费。
共享库 (shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接(dynamic linking),是由一个叫做动态链接器(dynamic linker)的程序来执行的。
共享库也称为共享目标(shared object),在Unix系统中通常用 .so
后缀来表示。微软的操作系统大量地利用了共享库,它们称为DLL(动态链接库)。
共享库是以两种不同的方式来“共享”的。首先,在任何给定的文件系统中,对于一个库只有一个 .so
文件。所有引用该库的可执行目标文件共享这个 .so
文件中的代码和数据,而不是像静态库的内容那样被拷贝到引用它们的可执行的文件中。其次,在存储器中,一个共享库的 .text
节的一个副本可以被不同的正在运行的进程共享。
unix> gcc -shared -fPIC -o libvector.so addvec.c multvec.c
unix> gcc -o p2 main.c ./libvector.so
链接过程如下图: