深入理解Linux静态库与动态库

前言

最近在写一个小型的http服务器,为了能够提供网页的动态内容,开始接触linux静态库和动态库的概念。之后又看了《深入理解计算机系统》的第七章–链接,在此对于linux静态库和动态库做一个总结。


为什么需要库

定义

程序一般需要经过预处理,编译,汇编和链接将我们编写ASII文本程序变成可执行的目标文件(.out)。然而在实际开发中,有许多模块是反复复用的,如printf函数,scanf函数等,故所有的编译系统都提供一种机制,将所有的目标模块打包成库,作为链接的输入,供开发者调用链接。

静态库

静态库的名字一般为libxxx.a, 静态库把相关的函数编译成独立的目标模块(.a),然后封装成一个单独的静态库文件。在链接时,将静态库嵌入到可执行目标文件中,链接器将只拷贝被程序引用的目标模块。生成静态库文件过程中的Linux命令为:gcc -c pro1.c pro2.c; ar rcs libxxx.a pro1.o pro2.o.静态库的优点是编译后的执行程序不需要外部函数库的支持,但是如果静态库更新了,那么我们就得重新编译程序,并且几乎每个C程序都会用到标准IO函数,如printf和scanf,那么在运行时,这些函数的代码会被复制到每个运行进程的文本段中,在运行多进程的系统上,这是对稀缺的存储器系统资源的浪费。

动态库

基于静态库的缺点,动态库(共享库)应运而生。动态库是一个目标模块(.so),在运行时可以被加载到任意的存储器地址,并在一个存储器中的程序链接起来。从文件系统角度来说,所用引用该动态库的可执行目标文件都共享这个.so文件的代码和数据,而不是像静态库那样将内容嵌入到引用它们的可执行目标文件中。从存储器系统角度来说,一个共享库的.text节的一个副本可以被不同正在运行的进程共享。


使用静态库

首先我们编写两个程序pro1.c 和pro2.c,代码如下:

//pro1.c
#include <stdio.h>
void print1()
{
    printf("This is the first lib!\n");
}

//pro2.c

#include <stdio.h>
void print2()
{
    printf("This is the second lib!\n");
}

然后我们编译这两个程序,并使用AR工具,生成.libpro.a文件

[yateslaw@study link]$ gcc -c pro1.c pro2.c
[yateslaw@study link]$ ar rcs libpro.a pro1.o pro2.o

之后再编写一个main.c

int main()
{
    print1();
    print2();
    return 0;
}

最后我们编译和链接main.o 和libpro.a,运行main程序

[yateslaw@study link]$ gcc -c main.c
[yateslaw@study link]$ gcc -o main main.o  ./libpro.a 
[yateslaw@study link]$ ./main
This is the first lib!
This is the second lib!

使用动态库

生成.so文件

首先编写add.c 和mul.c程序

//add.c
void add(int *x,int *y,int *z,int n)
{
    int i;

    for(i=0;i<n;i++)
        z[i]=x[i]+y[i];

}

//mult.c
void mult(int *x,int *y,int *z,int n)
{
    int i;

    for(i=0;i<n;i++)
        z[i]=x[i]*y[i];

}

生成.so文件Linux命令

[yateslaw@study dynamic]$ gcc -shared -fPIC -o libpro.so add.c mult.c

其中-fPIC选项指示编译器生成与位置无关的代码,-shared选项指示连接器创建一个共享的目标文件。


加载和链接共享库的相关函数
dlopen函数
#include <dlfcn.h>

void *dlopen(const char *filename, int flag);
返回:若成功返回指向句柄的指针,若出错则为NULL

dlopen函数加载和链接共享库filename。flag参数可以为RTLD_NOW,告诉链接器立即解析对外部符号的引用,而RTLD_LAZY则告诉链接器推迟符号解析直到执行来自库中的代码。RTLD_GLOBAL标志则表示上面两值任一都可以。

dlsym函数
#include <dlfcn.h>

void *dlsym(void *handle, char *symbol);
返回:若成功返回指向符号的指针,若出错则为NULL

dlsym函数的输入是一个指向前面已经打开共享库的句柄和一个符号名字,如果该符号存在,就返回符号的地址,否则返回NULL。

dlclose函数:卸载共享库
#include <dlfcn.h>

int dlclose(void *handle);
返回:若成功返回0,若出错则为-1
dlerror函数
#include <dlfcn.h>

const char *dlerror(void);
返回:如果前面对dlopen,dlsym,dlclose调用失败,则为错误消息,如果前面调用成功,则为NULL

讲完上面的函数以后,下面我们就利用libpro.so共享库来编写我们的main程序:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int x[2]={1,2};
int y[2]={3,4};
int z[2];

int main()
{
    void *handle;
    void (*add)(int *,int *,int *,int);
    char *error;

    handle=dlopen("./libpro.so",RTLD_LAZY);
    if(!handle)
    {
        fprintf(stderr,"%s\n",dlerror());
        exit(1);
    }

    add=dlsym(handle,"add");
    if((error=dlerror())!=NULL)
    {
        fprintf(stderr,"%s\n",dlerror());
        exit(1);
    }

    add(x,y,z,2);
    printf("z=[%d %d]\n",z[0],z[1]);

    if(dlclose(handle)<0)
    {
        fprintf(stderr,"%s\n",dlerror());
        exit(1);
    }
    return 0;
}

编译main.c程序并链接动态库libpro.so ,运行后结果如下:

[yateslaw@study dynamic]$ gcc -rdynamic -O2 -o main main.c -ldl
[yateslaw@study dynamic]$ ./main
z=[4 6]

总结比较动态库和静态库

  1. 多个目标文件可以被链接到一个单独的静态库,静态库被链接后直接嵌入到可执行文件中。这样有两个缺点,首先系统的存储器资源被浪费了,因为如果多个程序链接了同一个库,那么每一个生成的可执行文件都会有库的一个副本。其次,当需要更新静态库文件时,整个程序需要重新编译。
  2. 动态库的出现弥补了静态库的缺点。动态库是在程序运行时被链接的,在链接时.so文件的代码和数据节是没有拷贝到可执行目标文件的,反之,链接器只是拷贝了一些重定位和符号表的信息,使得运行时可以解析.so文件代码和数据的引用。当我们需要升级动态库时,只需使用新的.so文件替换原来的旧文件即可。

参考文献:

《深入理解计算机系统 第七章-链接》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值