Linux下动态库与静态库原理与制作

一. 什么是库

在系统中,库就是一个现有的,已经写好可供直接使用的代码,很多程序都依赖库;
通常,库大致分为两种:分别是动态库和静态库;

二. c程序的编译过程

先观察图:
在这里插入图片描述
通过图我们不难观察到,当程序的编译进行到链接时,由于使用的库不同(动态库和静态库)又有动态链接与静态链接两种;

三. 静态库

3.1 什么是静态库

之所以称为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结如下:

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

其实,说白了,就是当编译进行到链接时,如果使用的是静态链接,那么,系统就会将需要用到的静态库的代码全部与我们汇编完成的一个或者多个 .o 文件打包压缩成为一个可执行文件,正如上面所说,这样将会耗用大量的内存空间;所以,当我们使用gcc 进行编译时,默认使用动态链接,具体如何使用到静态链接,后面在做阐述;

3.2 如何使用静态链接

编写一个简单的程序 “hello.c” 示例;
编译时,加上选项 -static 可使用静态链接;

gcc hello.c -static

使用下面这个命令可以查看可执行文件的大小:

du -sh a.out

在这里插入图片描述
可以看到,一个简单的 hello.c 程序就足足有860k之多;

3.3 制作一个静态库

假设有这样一种情况,我们自己研究的一个算法,想要将其卖给他人使用,但是仅限于使用,而不想让别人知道我们这个算法的源码,我们就可以将其制作成为一个动态库或者静态库,将这个库给他(动态库和静态库文件时乱码),再给他一个如何使用算法中的函数的头文件(类似函数声明),这样就可以完成即让他使用了这个算法,有能保证源码的安全性,下面就来介绍如何制作一个静态库
首先,我简单写一个c文件,change.c ,这就是我假设的我要给别人的算法,其包括两个函数,分别是将输入的字符串转化成大写或小写:

#include <stdio.h>
#include <string.h>

int i = 0;

int S_to_B_change(char *buf,char *buff)
{

    if(!buf || !buff)
    {
        printf("%s:%d %s():Invalid input\n",__FILE__,__LINE__,__FUNCTION__);
        return -1;
    }

    for(i = 0;i < strlen(buf);i++)
    {
        if(buf[i] >= 'a' && buf[i] <= 'z')
        {
            buf[i] = buf[i] - 32;
        }
    }

    strcpy(buff,buf);

    return 0;

}

int B_to_S_change(char *buf,char *buff)
{
    if(!buf || !buff)
    {

这就是我们写好的一个算法,接下来,再写一个程序 text.c,用来使用我们即将制作的静态库:

#include <stdio.h>
#include "change.h"

int main(int argc, char *argv[])
{
    char     buf[] = "HeLlO WorLD,GOOd BYe!";
    char     buff[128];

    S_to_B_change(buf,buff);
    printf("After S_to_B_change:%s\n",buff);

    B_to_S_change(buf,buff);
    printf("After B_to_S_change:%s\n",buff);

    return 0;
}

这个程序就是将buf中的内容,使用算法中的函数,将其转化为大写和小写并打印;
需要注意的还,我们需要包含 change.h 这个头文件,其内容是:

#ifndef  __CHANGE__H_
#define  __CHANGE__H_

int S_to_B_change(char *buf,char *buff);

int B_to_S_change(char *buf,char *buff);

#endif   /* ----- #ifndef __CHANGE__H_  ----- */

制作静态库:

  1. gcc -c *.c
    将所有的c文件编译生成 .o 文件;

  2. ar -rcs libchange.a *.o
    将所有的 .o 文件打包生成一个静态库 .a 文件

在这里插入图片描述

可以看到已经生成了一个 .a 的静态库文件,我们只要把这个文件给别人即可;现在调用编译我们用来测试的程序 text.c

这里就要注意了,因为我们要使用到这个libchange.a静态库,所以,编译时,要加上 -lchange(-l 表示要链接的库,后面跟上库名,要去掉前后缀名)这个选项,但是,只是这样的话还是会出现问题,可以先试一下,再来看原因;

因为系统在就收到 -lchange 命令后将会到存放库的文件夹中寻找这个静态库,我的电脑是在 /usr/lib 的这个文件夹下,很显然,这个文件夹下并没有我们制作的 libchange.h 所以,一种方法是将我们自己制作好的静态库放到这个文件夹下,编译时就可以找到这个库了,但是,想要将我们制作的库放到 /usr/lib 下,是需要 root 权限的,所以,我们通常会加上 -L.
这个选项,-L 用来指定除了 /lib/usr 文件以外还要到其他路径寻找, . 表示当前路径,命令如下:

 gcc text.c -o text -lchenge -L.

在这里插入图片描述
现在,我们已经成功的使用我们编译的静态库了!

四. 动态库

4.1 什么是动态库

我们先试着分析一下静态库的缺点:

  1. 假设一个静态库大小为1M,如果有两千个程序都是用到这个库,那将会消耗2G的内存;
  2. 当静态库中的代码优化或者更改后,所以调用该静态库的程序都需要重新编译,这样太过于麻烦,对于用户来说,一个小小的改动,就需要重新下载整个文件;

而动态库的特点是:动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

换句话来说,如果选择默认的动态链接,程序在编译完成后很小,因为他只是告诉了程序,在运行是需要用到哪些动态库,而不会像静态库那样将代码直接拷贝;

4.2 动态库的制作

还是使用上面的几个代码:change.c , text.c;

  • gcc -fPIC -shared change.c -o libchange.so

PIC:Position Independent Code(位置无关代码),即这个 .so 文件 具体放在内存中的那个位置是不确定的,只有在链接的时候才决定;
这就制作好了一个动态库,也可以使用 *.c 让多个c文件共同生成一个动态库;那么动态库如何来使用呢?

在这里插入图片描述
可以看到已经生成了一个 libchange.so 的动态库,同样,编译时,需要使用命令:

gcc text.c -o text -lchange -L.

当 /usr/lib 文件夹下或者当前目录中有同名的动态库和静态库是,系统默认使用动态库,如果这种情况下想要使用静态库,加上先选项 -static 即可;编译成功后,执行程序:
在这里插入图片描述
没错,编译虽然成功,但却无法执行该程序,翻译一下错误:

 加载共享库时出错:libchange.so:无法打开共享对象文件:没有这样的文件或目录

这就与我们前面介绍的性质对应上了,因为在程序选择使用动态链接到动态库时,并不会将动态库中的代码拷贝到可执行文件中,而仅仅只是告诉可执行文件需要用到那个动态库,当可执行文件运行时,回到指定目录 /usr/lib 下去找到这个动态库并加载进来然而,我们并没有把 libchange.so 文件放到指定的文件夹中,所以就出现了“没有这样的文件或目录”错误;
一种方法,和前面一样,就是将这个动态库考到 /usr/lib 下,但是并不是所有人都有 root 权限;
第二种方法:在linux系统中,存在有个环境变量(在不同目录中都是不一样的)LD_LIBRARY_PATH,系统在寻找动态库时,会到两个地方去找,一个是我们提到的 /usr/lib 中,而另一个,便是该环境变量指定的路径;通过修改这个环境变量为当前的路径,就可以让系统除了到 /usr/lib 下寻找动态库,还可以到当前路径下寻找;
还需要注意一点,在更改时需要使用绝对路径,不能使用相对路径,pwd命令可以查看当前的绝对路径;
可以先使用pwd查看绝对路径后在赋值,也可以使用命令置换符 · ;
在这里插入图片描述
接下来,再来执行程序:
在这里插入图片描述
这样就成功了!

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/105372252