• 【Linux】动态库&静态库


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

    演示所用系统:centos7.6

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    生成好静态库后,我们可以用 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_动静态库]$ 
    
    • 1
    • 2
    • 3
    • 4

    2.2 动态库

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

    同时,依赖的.o文件也需要用-fPIC来编译

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

    其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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3.使用

    #include "myPrint.h"
    #include "myMath.h"
    #include "stdio.h"
    
    int main()
    {
        printf("ret %d\n",Add(1,2));
        Print("这是一个测试");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    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]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    • ""是在当前路径下找
    • <>是在库目录下面找

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

    3.1 静态库

    链接静态库的方法如下

    gcc test.c -L../lib-static/lib/ -I../lib-static/include/ -lmytest -o test
    
    • 1
    • -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]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    特点

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

    [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_动静态库]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果我们把自己的库的实现丢入了系统的库目录下(一般是/lib64/)编译的时候就不需要带-L选项了,只需要用-l指定库名即可

    gcc test.c -lmytest
    
    • 1

    但是将自己的库丢入系统库路径下的操作并不推荐,就和你将自己的可执行程序丢入/usr/bin路径里面一样,会污染系统的环境


    3.2 动态库

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

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

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

    [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]$ 
    
    • 1
    • 2
    • 3

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

    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]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    动态链接

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

    刚刚我们的指定只是告诉了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/
    
    • 1

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

    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/
    
    • 1

    再次运行./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: 这是一个测试
    
    • 1
    • 2
    • 3
    • 4

    修改配置文件的办法,便是将该路径永久写入环境变量(修改环境变量的操作只对当前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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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]$ 
    
    • 1
    • 2
    • 3
    • 4

    这里的操作非常简单,我们只需要在该目录下新增一个.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]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    [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]$ 
    
    • 1
    • 2
    • 3

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

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

    执行完该命令后,可执行程序也能成功运行了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]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    sudo rm /etc/ld.so.conf.d/mytest.conf
    sudo ldconfig
    
    • 1
    • 2

    这样做是避免污染

    3.3.3 在lib64下创建一个软连接

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

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

    这部分就不做演示了


    4.优劣

    4.1 静态库

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

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

    4.2 动态库

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

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

    image-20221103123717325

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

    image-20221103124031505

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

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

    5.动态库-fPIC的作用

    参考https://blog.csdn.net/itworld123/article/details/117587091

    gcc -fPIC -c myMath.c -o myMath.o
    
    • 1

    fPIC 的全称是 Position Independent Code, 用于生成位置无关代码

    什么是位置无关代码?

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

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

    int fuc(int a)
    {
    	return ++a;
    }
    
    • 1
    • 2
    • 3
    • 4

    这时候,就可以再编译的时候不带-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
    
    • 1
    • 2

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

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

    结语

    动静态库的基本认识到这里就OVER辣,大家也可以去尝试下载一些第三方的库来使用,比如在树莓派上最常用的wiringPi库,还有C++的boost库等等

    sudo yum install -y boost-devel
    
    • 1

    有什么问题,可以在评论区提出哦!

    image-20221103120720282

  • 相关阅读:
    starrock通过导入实现数据变更
    docker进阶——docker网络简解
    很多人都想要短视频行业创业,总结的几个常见问题,希望对你有所帮助!
    RHCSA8.2
    【我的Python学习笔记】从基础数据类型到爬虫,后附学姐Python爬虫笔记
    SpringBoot使用git-commit-id-maven-plugin打包
    专家解读 | 电力行业关基测评安全防护新挑战
    农业物联网
    C++中的decltype、std::declval 和 std::decay_t傻傻分不清楚
    豆瓣评分9.8,阿里内部的分布式架构手册让多少人突破了瓶颈?
  • 原文地址:https://blog.csdn.net/muxuen/article/details/127668016