Linux动态库和静态库

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

动静态库的基本原理

动静态库的本质是可执行程序的“半成品”。

我们都知道一堆源文件和头文件最终变成一个可执行程序需要经历以下四个步骤

  1. 预处理 完成头文件展开、去注释、宏替换、条件编译等最终形成xxx.i文件。
  2. 编译 完成词法分析、语法分析、语义分析、符号汇总等检查无误后将代码翻译成汇编指令最终形成 xxx.s文件。
  3. 汇编 将汇编指令转换成二进制指令最终形成xxx.o文件。
  4. 链接 将生成的各个xxx.o文件进行链接最终形成可执行程序。

例如用test1.c、test2.c、test3.c、test4.c以及main1.c形成可执行文件我们需要先得到各个文件的目标文件test1.o、test2.o、test3.o、test4.o以及main1.o然后再将这写目标文件链接起来最终形成一个可执行程序。
image.png
如果我们在另一个项目当中也需要用到test1.c、test2.c、test3.c、test4.c和项目的main2.c或者main3.c分别形成可执行程序那么可执行程序生成的步骤也是一样的。image.png
而实际上对于可能频繁用到的源文件比如这里的test1.c、test2.c、test3.c、test4.c我们可以将它们的目标文件test1.o、test2.o、test3.o、test4.o进行打包之后需要用到这四个目标文件时就可以直接链接这个包当中的目标文件了而这个包实际上就可以称之为一个库。
image.png
实际上所有库本质都是一堆目标文件(xxx.o)的集合库的文件当中并不包含主函数而只是包含了大量的方法以供调用所以说动静态库本质是可执行程序的“半成品”。

认识动静态库

在Linux下创建文件编写以下代码并生成可执行程序。

#include <stdio.h> 
int main() 
{     
    printf("hello world\n"); //库函数     
    return 0; 
}

这是最简单的代码运行结果大家也都知道就是hello world。
image.png


下面我们就通过这份简单的代码来认识一下动静态库
在这份代码当中我们可以通过调用printf输出hello world主要原因是gcc编译器在生成可执行程序时将C标准库也链接进来了。
在Linux下我们可以通过ldd 文件名来查看一个可执行程序所依赖的库文件。image.png
这其中的libc.so.6就是该可执行程序所依赖的库文件我们通过ls命令可以发现libc.so.6实际上只是一个软链接。
image.png
实际上该软链接的源文件libc-2.17.so和libc.so.6在同一个目录下为了进一步了解我们可以通过file 文件名命令来查看libc-2.17.so的文件类型。image.png
此时我们可以看到libc-2.17.so实际上就是一个共享的目标文件库准确来说这还是一个动态库。

  • 在Linux当中以.so为后缀的是动态库以.a为后缀的是静态库。
  • 在Windows当中以.dll为后缀的是动态库以.lib为后缀的是静态库

这里可执行程序所依赖的libc.so.6实际上就是C动态库当我们去掉一个动静态库的前缀lib再去掉后缀.so或者.a及其后面的版本号剩下的就是这个库的名字。

而gcc/g++编译器默认都是动态链接的若想进行静态链接可以携带一个-static选项。
[chr@VM-4-9-centos lesson_01_17]$ gcc -o mytest-s test.c -static
image.png
此时生成的可执行程序就是静态链接的了可以明显发现静态链接生成的可执行程序的文件大小比动态链接生成的可执行程序的文件大小要大得多。
静态链接生成的可执行程序并不依赖其他库文件此时当我们使用ldd 文件名命令查看该可执行程序所依赖的库文件时就会看到以下信息。
image.png

此外当我们分别查看动静态链接生成的可执行程序的文件类型时也可以看到它们分别是动态链接和静态链接的。image.png

动静态库各自的特征

静态库
静态库是程序在编译链接的时候把库的代码复制到可执行文件当中的生成的可执行程序在运行的时候将不再需要静态库因此使用静态库生成的可执行程序的大小一般比较大。

优点

  • 使用静态库生成可执行程序后该可执行程序就可以独自运行不再需要库了。

缺点

  • 使用静态库生成可执行程序会占用大量空间特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库这时在内存当中就会存在大量的重复代码。

动态库

动态库是程序在运行的时候才去链接相应的动态库代码的多个程序共享使用库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表而不是外部函数所在目标文件的整个机器码。

在可执行文件开始运行前外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中这个过程称为动态链接。动态库在多个程序间共享节省了磁盘空间操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用节省了内存和磁盘空间。
image.png

优点

  • 节省磁盘空间且多个用到相同动态库的程序同时运行时库文件会通过进程地址空间进行共享内存当中不会存在重复代码。

缺点

  • 必须依赖动态库否则无法运行。

    静态库的打包与使用

    为了更容易理解下面演示动静态库的打包与使用时都以下面的四个文件为例其中两个源文件add.c和sub.c两个头文件add.h和sub.h。

add.h当中的内容如下

#pragma once

extern int my_add(int x, int y);

add.c当中的内容如下

#include "add.h"

int my_add(int x, int y)
{
    return x + y;
}

sub.h当中的内容如下

#pragma once

extern int my_sub(int x, int y);

sub.c当中的内容如下

#include "sub.h"

int my_sub(int x, int y)
{
    return x - y;
}

打包

下面我们就利用这四个文件打包生成一个静态库image.png

第一步让所有源文件生成对应的目标文件

image.png

第二步使用ar命令将所有目标文件打包为静态库

ar命令是gnu的归档工具常用于将目标文件打包为静态库下面我们使用ar命令的-r选项和-c选项进行打包。
[chr@VM-4-9-centos lesson_01_17]$ ar -rc libcal.a add.o sub.o

  • -r(replace)若静态库文件当中的目标文件有更新则用新的目标文件替换旧的目标文件。
  • -c(create)建立静态库文件。

image.png
此外我们可以用ar命令的-t选项和-v选项查看静态库当中的文件。

  • -t列出静态库中的文件。
  • -v(verbose)显示详细的信息。

[chr@VM-4-9-centos lesson_01_17]$ ar -tv libcal.a
image.png

第三步将头文件和生成的静态库组织起来

当我们把自己的库给别人用的时候实际上需要给别人两个文件夹一个文件夹下面放的是一堆头文件的集合另一个文件夹下面放的是所有的库文件。

因此在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下将生成的静态库文件libcal.a放到一个名为lib的目录下然后将这两个目录都放到mathlib下此时就可以将mathlib给别人使用了。
image.png

使用Makefile

当然我们可以将上述所要执行的命令全部写到Makefile当中后续当我们要生成静态库以及组织头文件和库文件时就可以一步到位了不至于每次重新生成的时候都要敲这么多命令这也体现了Makefile的强大。

mylib=libcal.a
   CC=gcc 

   $(mylib):add.o sub.o
      ar -rc -o $(mylib) $^
   %.o:%.c
      $(CC) -c $<

   .PHONY:clean 
  clean:
     rm -f $(mylib) ./*.o

  .PHONY:output
  output:
     mkdir -p mathlib/include
     mkdir -p mathlib/lib 
     cp ./*.h mathlib/include
     cp ./*.a mathlib/lib 

image.png
编写Makefile后只需一个make就能生成所有源文件对应的目标文件进而生成静态库。
一个make output就能将头文件和静态库组织起来。
image.png

使用

创建源文件main.c编写下面这段简单的程序来尝试使用我们打包好的静态库。

#include <stdio.h>
#include <add.h>

int main()
{
    int x = 20;
    int y = 10;
    int z = my_add(x, y);
    printf("%d + %d = %d\n", x, y, z);
    return 0;
}

方法一使用选项

此时使用gcc编译main.c生成可执行程序时需要携带三个选项
[chr@VM-4-9-centos lesson_01_17]$ gcc main.c -I./mathlib/include -L./mathlib/lib -lcal

  • -I指定头文件搜索路径。
  • -L指定库文件搜索路径。
  • -l指明需要链接库文件路径下的哪一个库。

image.png
此时就可以成功使用我们自己打包的库文件并生成可执行程序。image.png

说明一下

  1. 因为编译器不知道你所包含的头文件add.h在哪里所以需要指定头文件的搜索路径。
  2. 因为头文件add.h当中只有my_add函数的声明并没有该函数的定义所以还需要指定所要链接库文件的搜索路径。
  3. 实际中在库文件的lib目录下可能会有大量的库文件因此我们需要指明需要链接库文件路径下的哪一个库。库文件名去掉前缀lib再去掉后缀.so或者.a及其后面的版本号剩下的就是这个库的名字。
  4. -I-L-l这三个选项后面可以加空格也可以不加空格。

方法二把头文件和库文件拷贝到系统路径下

既然编译器找不到我们的头文件和库文件那么我们直接将头文件和库文件拷贝到系统路径下不就行了。
[chr@VM-4-9-centos lesson_01_17]$ sudo cp mathlib/include/* /usr/include/
[chr@VM-4-9-centos lesson_01_17]$ sudo cp mathlib/lib/libcal.a /lib64/

需要注意的是虽然已经将头文件和库文件拷贝到系统路径下但当我们使用gcc编译main.c生成可执行程序时还是需要指明需要链接库文件路径下的哪一个库。此时才能成功使用我们自己打包的库文件并生成可执行程序。
image.png

为什么之前使用gcc编译的时候没有指明过库名字

因为我们使用gcc编译的是C语言而gcc就是用来编译C程序的所以gcc编译的时候默认就找的是C库但此时我们要链接的是哪一个库编译器是不知道的因此我们还是需要使用-l选项指明需要链接库文件路径下的哪一个库。

扩展
实际上我们拷贝头文件和库文件到系统路径下的过程就是安装库的过程。但并不推荐将自己写的头文件和库文件拷贝到系统路径下这样做会对系统文件造成污染。

动态库的打包与使用

打包

动态库的打包相对于静态库来说有一点点差别但大致相同我们还是利用这四个文件进行打包演示image.png

第一步让所有源文件生成对应的目标文件

此时用源文件生成目标文件时需要携带-fPIC选项

  • -fPIC(position independent code)产生位置无关码。

image.png

说明

  • -fPIC作用于编译阶段告诉编译器产生与位置无关的代码此时产生的代码中没有绝对地址全部都使用相对地址从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的共享库被加载时在内存的位置不是固定的。
  • 如果不加-fPIC选项则加载.so文件的代码段时代码段引用的数据对象需要重定位重定位会修改代码段的内容这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的拷贝并且每个拷贝都不一样取决于这个.so文件代码段和数据段内存映射的位置。
  • 不加-fPIC编译出来的.so是要在加载时根据加载到的位置再次重定位的因为它里面的代码BBS位置无关代码。如果该.so文件被多个应用程序共同使用那么它们必须每个程序维护一份.so的代码副本(因为.so被每个程序加载的位置都不同显然这些重定位后的代码也不同当然不能共享)。
  • 我们总是用-fPIC来生成.so但从来不用-fPIC来生成.a。但是.so一样可以不用-fPIC选项进行编译只是这样的.so必须要在加载到用户程序的地址空间时重定向所有表目。

第二步使用-shared选项将所有目标文件打包为动态库

与生成静态库不同的是生成动态库时我们不必使用ar命令我们只需使用gcc的-shared选项即可。image.png

第三步将头文件和生成的动态库组织起来

与生成静态库时一样为了方便别人使用在这里我们可以将add.h和sub.h这两个头文件放到一个名为include的目录下将生成的动态库文件libcal.so放到一个名为lib的目录下然后将这两个目录都放到mlib下此时就可以将mlib给别人使用了。
image.png

使用Makefile

当然生成动态库也可以将上述所要执行的命令全部写到Makefile当中后续当我们要生成动态库以及组织头文件和库文件时就可以一步到位了。

mylib=libcal.a
   CC=gcc

   $(mylib):add.o sub.o
      $(CC) -shared -o $(mylib) $^                                                                    
   %.o:%.c             
      $(CC) -fPIC -c $<

   .PHONY:clean 
  clean:
     rm -f $(mylib) ./*.o

  .PHONY:output
  output:
     mkdir -p mlib/include
     mkdir -p mlib/lib 
     cp ./*.h mlib/include
     cp ./*.so mlib/lib

image.png
编写Makefile后只需一个make就能生成所有源文件对应的目标文件进而生成动态库。
一个make output就能将头文件和动态库组织起来。
image.png

使用

我们还是用刚才使用过的main.c来演示动态库的使用。

#include <stdio.h>
#include <add.h>

int main()
{
    int x = 20;
    int y = 10;
    int z = my_add(x, y);
    printf("%d + %d = %d\n", x, y, z);
    return 0;
}

说明一下使用该动态库的方法与刚才我们使用静态库的方法一样我们既可以使用 -I-L-l这三个选项来生成可执行程序也可以先将头文件和库文件拷贝到系统目录下然后仅使用-l选项指明需要链接的库名字来生成可执行程序下面我们仅以第一种方法为例进行演示。

此时使用gcc编译main.c生成可执行程序时需要用-I选项指定头文件搜索路径用-L选项指定库文件搜索路径最后用-l选项指明需要链接库文件路径下的哪一个库。
[chr@VM-4-9-centos lesson_01_17]$ cc main.c -I./mlib/include -L./mlib/lib -lcal
image.png

与静态库的使用不同的是此时我们生成的可执行程序并不能直接运行。image.png
需要注意的是我们使用-I-L-l这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁但是当生成的可执行程序生成后就与编译器没有关系了此后该可执行程序运行起来后操作系统找不到该可执行程序所依赖的动态库我们可以使用ldd命令进行查看。
image.png
可以看到此时可执行程序所依赖的动态库是没有被找到的。

解决该问题的方法有以下三个

方法一拷贝.so文件到系统共享库路径下

既然系统找不到我们的库文件那么我们直接将库文件拷贝到系统共享的库路径下这样一来系统就能够找到对应的库文件了。可执行程序也就能够顺利运行了。
[chr@VM-4-9-centos lesson_01_17]$ sudo cp mlib/lib/libcal.so /lib64
image.png

方法二更改LD_LIBRARY_PATH

LD_LIBRARY_PATH是程序运行动态查找库时所要搜索的路径我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可。
[chr@VM-4-9-centos lesson_01_17]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/cl/BasicIO/testlib/project/mlib/lib
image.png
此时我们再用ldd命令查看该可执行程序就会发现系统现在就可以找到该可执行程序所依赖的动态库了。image.png

方法三配置/etc/ld.so.conf.d/

我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件而这些配置文件当中存放的都是路径系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下那么当可执行程序运行时系统就能够找到我们的库文件了。
首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。
image.png
然后将该.conf文件拷贝到/etc/ld.so.conf.d/目录下。image.png
但此时我们用ldd命令查看可执行程序时发现系统还是没有找到该可执行程序所依赖的动态库。
这时我们需要使用ldconfig命令将配置文件更新一下更新之后系统就可以找到该可执行程序所依赖的动态库了。然后便能正常运行
[chr@VM-4-9-centos lesson_01_17]$sudo ldconfig
image.png

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux