linux静态库和动态库

基本概念

库从本质上来说是一种可执行代码的二进制格式,可以被载入到内存中执行。库分为静态库和动态库两种。

在Linux下,静态库的名字一般为libxxx.a,其中xxx为库的名字,使用静态库时,整个库的所有数据都会被整合到目标代码中,因而文件比较大,执行时不再需要外部库的支持,如果库函数有变化,程序必须重新编译。

动态库的名字一般是libxxx.M.N.so,其中xxx为库名,M是主版本号,N是副版本号,版本号是可选的,但名字必须要有。动态库在编译时并没有被编进目标代码中,程序执行到相关函数时才调用库中的相应函数,因此可执行文件较小,程序运行环境必须提供相应的动态库。动态库的修改不影响程序,因而升级比较方便。

对于静态库,链接器会找出程序需要的函数,将它们拷到执行文件,由于这种拷贝是完整的,一旦链接成功,静态库也就不再需要了。对于动态库,则会在程序内打个标记指明程序运行时,要先载入这个库。由于动态库节省空间,在链接时会优先去链动态库,即如果同时存在静态库和动态库,不特别指定的话,将与动态库链接。

静态库的制作与使用

为方便说明,建立add.c和sub.c两个文件,分别编写加法和减法接口如下:

int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}

编译生成静态库文件:

# gcc -c add.c sub.c
# ar -rsv libtest.a add.o sub.o

这样,静态库是做好了,用ar -t libtest.a可查看该库包含哪些obj文件。接下来写程序调用静态库中的接口。

#include <stdio.h>
int main()
{
    int x, y;
    while (~scanf("%d%d", &x, &y))
        printf("%d %d\n", add(x, y), sub(x, y));
    return 0;
}

在编译时,指定链接静态库,得到的可执行程序可独立运行。

# gcc -o call call.c -L. -ltest

动态库的制作和使用

当程序因动态库问题无法运行时,可用ldd命令查看该程序到底依赖了哪些so文件。在Linux系统中,动态链接库的配置文件一般在/etc/ld.so.conf文件内,该文件会包含/etc/ld.so.conf.d目录。当修改了ld.so.conf或者/ld.so.conf.d目录下的文件,或者往该目录下拷贝了新的动态库时,要执行ldconfig命令,它负责搜索/lib, /usr/lib以及/etc/ld.so.conf中所列的目录下可用的动态链接库文件到缓存/etc/ld.so.cache中。

执行ldconfig时,如果带了目录,则表示在缓存/etc/ld.so.cache中追加指定目录下的共享库;如果是单独运行,则它只会搜索/lib, /usr/lib和/etc/ld.so.conf列出的目录,重建/etc/ld.so.cache。

生成动态库可先生成obj文件,再先成so文件:

# gcc -c -fPIC add.c sub.c
# gcc -o libtest.so -fPIC -shared add.o sub.o

也可以一步到位:

# gcc -o libtest.so -fPIC -shared add.c sub.c

其中,-shared选项指定生成动态链接库,不用该标识外部程序将无法连接;-fPIC表示编译为位置独立的代码,不用此选项的话生成的代码是位置相关的,不能达到真正共享代码段的目的。

动态库的使用有两种方式:动态链接和动态加载。下面分别示例说明。

动态链接

编译时指定链接动态库:

# gcc -o call call.c -L. -ltest

要让生成的程序能够运行,可以执行ldconfig `pwd`命令,也可将动态库加到搜索范围内并执行ldconfig命令。

动态加载

Linux提供了一组API用于动态加载so文件。

#include <dlfcn.h>
void* dlopen(const char *filename, int flag);
char* dlerror(void);
void* dlsym(void *handle, const char *symbol);
int dlclose(void *handle);

说明:

#include <stdio.h>
#include <dlfcn.h>
typedef int (*Func)(int, int);
int main()
{
    void *handle = dlopen("libtest.so", RTLD_LAZY);
    if (dlerror()) perror("dlopen");
    Func fnadd = dlsym(handle, "add");
    if (dlerror()) perror("load add");
    Func fnsub = dlsym(handle, "sub");
    if (dlerror()) perror("load sub");

    int x, y;
    while (~scanf("%d%d", &x, &y))
        printf("%d %d\n", fnadd(x, y), fnsub(x, y));
    
    dlclose(handle);
    return 0;
}

如果采用动态加载方式,编译时须指定-rdynamic -ldl选项。

# gcc -o call call.c -rdynamic -ldl
Table of Contents