天天看点

【Linux】动态库&静态库

在最初学习GCC的使用的时候,提到了动态、静态库的创建办法。今天就让我们来详细了解一番,它们之间究竟有何不同吧!

演示所用系统:centos7.6

文章目录

  • ​​1.动态库和静态库​​
  • ​​2.生成​​
  • ​​2.1 静态库​​
  • ​​2.2 动态库​​
  • ​​2.3 一并发布​​
  • ​​3.使用​​
  • ​​3.1 静态库​​
  • ​​特点​​
  • ​​3.2 动态库​​
  • ​​ldd命令​​
  • ​​动态链接​​
  • ​​3.3 找到动态库​​
  • ​​3.3.1 环境变量LD_LIBRARY_PATH​​
  • ​​3.3.2 /etc/ld.so.conf.d​​
  • ​​3.3.3 在lib64下创建一个软连接​​
  • ​​4.优劣​​
  • ​​4.1 静态库​​
  • ​​4.2 动态库​​
  • ​​5.动态库-fPIC的作用​​
  • ​​结语​​

1.动态库和静态库

先来了解一下动态库和静态库的基本概念吧!

  • 静态库​

    ​.a​

    ​ 程序编译链接的时候,把静态库的代码连接到自己的可执行程序中,程序运行的时候将不再需要静态库
  • 动态库​

    ​.so​

    ​ 程序在运行的时候才去链接动态库的代码,多个程序共享库的代码

2.生成

测试所用代码 👉 ​​点我​​

我写好了两个头文件和两个源文件,为了减少博客篇幅,此处只贴出​

​.c​

​的函数实现

//myMath.c
#include"myMath.h"
int Add(int a,int b)
{
    return a+b;
}

//myPrint.c
#include "myPrint.h"
void Print(const char* msg)
{
    printf("time: %d, msg: %s\n",(unsigned int)time(NULL),msg);
}      

2.1 静态库

生成静态库所用命令为​

​ar -rc​

​,对应的完整make操作如下

libMytest.a:myMath.o myPrint.o
  ar -rc libMytest.a myMath.o myPrint.o
myMath.o:myMath.c
  gcc -c myMath.c -o myMath.o
myPrint.o:myPrint.c
  gcc -c myPrint.c -o myPrint.o      

生成好静态库后,我们可以用​

​ ar -tv​

​命令来查看该库的目录列表

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ ar -tv libmytest.a
rw-rw-r-- 1001/1001   1240 Nov  3 09:28 2022 myMath.o
rw-rw-r-- 1001/1001   1632 Nov  3 09:28 2022 myPrint.o
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$      

2.2 动态库

动态库的生成无需额外的命令,只需要在gcc编译的时候,指定​

​-shared​

​即可

同时,依赖的​

​.o​

​​文件也需要用​

​-fPIC​

​来编译

-fPIC 与位置无关码,和动态库的特性有关
-shared 代表需要编译一个动态库      

其make操作如下

libmytest.so:myMath.o myPrint.o
  gcc -shared -o libmytest.so myMath.o myPrint.o
myMath.o:myMath.c
  gcc -fPIC -c myMath.c -o myMath.o
myPrint.o:myPrint.c
  gcc -fPIC -c myPrint.c -o myPrint.o      

2.3 一并发布

这里我写了一个更加完整的makefile,可以同时编译生成动静态库,并将其打包到一个指定的文件夹内

.PHONY:all
all:libmytest.so libmytest.a

.PHONY:lib
lib:
  mkdir -p lib-static/lib
  mkdir -p lib-static/include
  cp *.a lib-static/lib
  cp *.h lib-static/include
  mkdir -p lib-dynamic/lib
  mkdir -p lib-dynamic/include
  cp *.so lib-dynamic/lib
  cp *.h lib-dynamic/include

libmytest.so:myMath.o myPrint.o
  gcc -shared -o libmytest.so myMath.o myPrint.o
myMath.o:myMath.c
  gcc -fPIC -c myMath.c -o myMath.o
myPrint.o:myPrint.c
  gcc -fPIC -c myPrint.c -o myPrint.o

libmytest.a:myMath.o myPrint.o
  ar -rc libmytest.a myMath.o myPrint.o
myMath_s.o:myMath.c
  gcc -c myMath.c -o myMath_s.o
myPrint_s.o:myPrint.c
  gcc -c myPrint.c -o myPrint_s.o

.PHONY:clean
clean:
  rm -rf *.o *.a *.so lib-static lib-dynamic      

3.使用

#include "myPrint.h"
#include "myMath.h"
#include "stdio.h"

int main()
{
    printf("ret %d\n",Add(1,2));
    Print("这是一个测试");
    return 0;
}      

当我们使用了动静态库后,就没有办法直接编译这个可执行程序了

muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ gcc test.c
/tmp/ccKHwYHv.o: In function `main':
test.c:(.text+0xf): undefined reference to `Add'
test.c:(.text+0x2a): undefined reference to `Print'
collect2: error: ld returned 1 exit status
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

这是因为,gcc没办法找到我们对应的头文件

  • ​""​

    ​是在当前路径下找
  • ​<>​

    ​是在库目录下面找

因为我们的头文件既不在当前路径,也不在系统的库中,所以gcc就没有办法找到头文件和函数声明

3.1 静态库

链接静态库的方法如下

gcc test.c -L../lib-static/lib/ -I../lib-static/include/ -lmytest -o test      
  • ​-L​

    ​选项后带的是库的路径
  • ​-I​

    ​选择后带的是头文件的搜索路径
  • ​-l(小写的L)​

    ​​选项带的是库的名字,需要去掉库文件名前面的​

    ​lib​

    ​​和后缀​

    ​.a​

  • ​-o test​

    ​代表生成可执行文件名为test
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ gcc test.c -L../lib-static/lib/ -I ../lib-static/include/ -lmytest -o test
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./test
ret 3
time: 1667441311, msg: 这是一个测试
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

特点

静态库的特点便是,其库的实现已经被编译链接进入了可执行程序,即便我们将库给删除,也不影响可执行程序的运行

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ make clean
rm -rf *.o *.a *.so lib-static lib-dynamic
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ ll
total 24
-rw-rw-r-- 1 muxue muxue  702 Nov  3 09:28 makefile
-rw-rw-r-- 1 muxue muxue   60 Nov  3 08:52 myMath.c
-rw-rw-r-- 1 muxue muxue   35 Nov  3 08:51 myMath.h
-rw-rw-r-- 1 muxue muxue  117 Nov  3 09:01 myPrint.c
-rw-rw-r-- 1 muxue muxue   77 Nov  3 09:01 myPrint.h
drwxrwxr-x 2 muxue muxue 4096 Nov  3 09:50 test
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$ ./test/./test
ret 3
time: 1667440486, msg: 这是一个测试
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库]$      

如果我们把自己的库的实现丢入了系统的库目录下(一般是​

​/lib64/​

​​)编译的时候就不需要带​

​-L​

​​选项了,只需要用​

​-l​

​指定库名即可

gcc test.c -lmytest      

但是将自己的库丢入系统库路径下的操作并不推荐,就和你将自己的可执行程序丢入​

​/usr/bin​

​路径里面一样,会污染系统的环境

3.2 动态库

动态库和静态库链接的基本方式是一样的

gcc test.c -L../lib-dynamic/lib/ -I ../lib-dynamic/include/ -lmytest -o testd      

这里选项的含义和上面完全一致,不同的是运行的时候

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd
./testd: error while loading shared libraries: libmytest.so: cannot open shared object file: No such file or directory
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

直接运行,你会发现报错了!这个报错的大概意思就是找不到动态库文件

ldd命令

使用​

​ldd​

​​命令查看​

​testd​

​可执行文件的动态库结构,会发现我们自己的库是没有找到的

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ldd testd
        linux-vdso.so.1 =>  (0x00007ffd051fe000)
        /$LIB/libonion.so => /lib64/libonion.so (0x00007f7de6d19000)
        libmytest.so => not found
        libc.so.6 => /lib64/libc.so.6 (0x00007f7de6832000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f7de662e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7de6c00000)
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

这是因为,动态库的特点便是运行的时候也需要指定!这是一个动态链接的过程!

动态链接

动态库需要执行​

​动态链接​

​:在可执行程序开始运行之前,外部函数的机器码由操作系统从磁盘上的该动态库复制到内存中

刚刚我们的指定只是告诉了​

​gcc​

​编译器库路径在哪儿,但是可执行程序运行的时候并不知道!

那么如何让可执行程序找到我们的动态库呢?

  • 将动态库拷贝到系统的​

    ​/lib64​

    ​文件夹中
  • 通过修改环境变量的方式,类似于​

    ​PATH​

    ​​,可执行程序运行的时候,会自动到​

    ​LD_LIBRARY_PATH​

    ​里面找动态库
  • 修改系统配置文件

3.3 找到动态库

3.3.1 环境变量LD_LIBRARY_PATH

和修改​

​PATH​

​的环境变量一样,我们可以通过修改环境变量的方式增加动态库的查找路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/      

修改了之后的环境变量如下

LD_LIBRARY_PATH=:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/      

再次运行​

​./testd​

​ 成功执行!

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd
ret 3
time: 1667443224, msg: 这是一个测试      

修改配置文件的办法,便是将该路径永久写入环境变量(修改环境变量的操作只对当前bash有效)这里就不演示辣!

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ldd testd
        linux-vdso.so.1 =>  (0x00007ffde04ea000)
        /$LIB/libonion.so => /lib64/libonion.so (0x00007fe9000bc000)
        libmytest.so => /home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/libmytest.so (0x00007fe8ffda1000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fe8ff9d3000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007fe8ff7cf000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe8fffa3000)      

ldd命令的结果也显示出了我们自己写的动态库的路径

3.3.2 /etc/ld.so.conf.d

除了修改环境变量,我们还可以修改​

​/etc/ld.so.conf.d​

​下的文件

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ls /etc/ld.so.conf.d
bind-export-x86_64.conf  kernel-3.10.0-1160.62.1.el7.x86_64.conf  kernel-3.10.0-1160.76.1.el7.x86_64.conf
dyninst-x86_64.conf      kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

这里的操作非常简单,我们只需要在该目录下新增一个​

​.conf​

​文件,并在里面写入动态库的绝对路径即可!

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ls /etc/ld.so.conf.d
bind-export-x86_64.conf  kernel-3.10.0-1160.62.1.el7.x86_64.conf  kernel-3.10.0-1160.76.1.el7.x86_64.conf  mytest.conf
dyninst-x86_64.conf      kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ cat /etc/ld.so.conf.d/mytest.conf
/home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

设置了之后,第一次运行,还是显示找不到动态库

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd
./testd: error while loading shared libraries: libmytest.so: cannot open shared object file: No such file or directory
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

我们只需要执行下面的命令让配文件生效,就OK了!

sudo ldconfig #子用户权限不够,需要加sudo      

执行完该命令后,可执行程序也能成功运行了1

[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ sudo ldconfig
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$ ./testd
ret 3
time: 1667448942, msg: 这是一个测试
[muxue@bt-7274:~/git/linux/code/22-11-02_动静态库/test]$      

测试完毕之后,建议将配置文件删除,并重新加载动态库配置文件

sudo rm /etc/ld.so.conf.d/mytest.conf
sudo ldconfig      

这样做是避免污染

3.3.3 在lib64下创建一个软连接

ln -s /home/muxue/git/linux/code/22-11-02_动静态库/lib-dynamic/lib/libmytest.so /lib64/libmytest.so      

创建软连接的方式和将我们的文件复制进去本质是一样的,只不过软连接只是一个快捷方式,如果我们把源给删了,软连接也会失效

这部分就不做演示了

4.优劣

4.1 静态库

静态库编译之后的可执行程序可以脱离静态库运行,也不需要知道库的路径。

即便这个库被删除,也丝毫不影响我们的可执行程序

4.2 动态库

动态库的代码只需要一份,所有的可执行程序便都可以使用

在运行期间,动态库可以被多个进程所共享。但前提是,可执行程序需要知道该动态库的路径,以便将其加载到内存中(或者找到它在内存中的位置)

【Linux】动态库&amp;静态库

这样就保证了多个进程同时使用同一个库,节省了内存的消耗,也节省了磁盘空间

【Linux】动态库&amp;静态库

这里动态库的可执行文件大小,小于静态库的可执行文件

因为测试的代码不多,所以差距尚不明显

5.动态库-fPIC的作用

gcc -fPIC -c myMath.c -o myMath.o      

fPIC 的全称是 ​

​Position Independent Code​

​, 用于生成位置无关代码

什么是位置无关代码?

个人理解是代码无绝对跳转,跳转都为相对跳转

如果我们的静态库中,不使用其他库的代码(比如​

​stdio.h​

​)

int fuc(int a)
{
  return ++a;
}      

这时候,就可以再编译的时候不带​

​-fPIC ​

​否则会报错

/usr/bin/ld: /tmp/ccCViivC.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/tmp/ccCViivC.o: could not read symbols: Bad value      

但显然,这种情况是非常少见的,所以我们一般编译动态库的时候,都需要带上这个参数,来实现真正意义上的动态库编译

  • 加 fPIC 选项生成的动态库,显然是位置无关的,这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能正确执行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量 / 函数的地址。
  • 加 fPIC 选项的源文件对于它引用的函数头文件编写有很宽松的尺度。比如只需要包含个声明的函数的头文件,即使没有相应的 C 文件来实现,编译成 so 库照样可以通过。
  • 对于不加 fPIC,则加载 so 文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so 文件代码段的进程在内核里都会生成这个 .so 文件代码段的 copy。每个 copy 都不一样,取决于这个 .so 文件代码段和数据段内存映射的位置。这种方式更消耗内存,优点是加载速度可能会快一丢丢,​

    ​弊大于利​

结语

sudo yum install -y boost-devel