根据链接时期不同,库分为静态库和动态库。静态库是在程序编译时链接的,动态库是在程序运行时链接的。

  • 静态库是目标文件的集合,调用静态库就是把静态库中的二进制指令拷贝到可执行文件中。
  • 共享库就是带入口的可执行文件,调用共享库其实就是跳转到共享库所在的二进制指令所在的位置,当可执行文件执行时,共享库会被一起加载到内存中。

§ gcc制作静态库和动态库 (Linux) - 图1

一、gcc制作静态库

当你和别人做项目合作,你不可能直接把源代码给别人,那样别人就可以自己开发,因为源代码就是你的核心技术。你不应该卖给他源代码,而是应该是程序,这样你就可以根据他有什么需求进行改或添加什么功能模块等,就可以改一次就可以收费一次,这样就可以有一个长期合作。

那应该给到客户的是什么呢?

  • 生成的库
  • 头文件

这样把生成的库头文件给客户也能够使用,只是他不知道里面具体怎么实现的。这样二者才能维持一个长期的合作

头文件对应的.c文件都被打包到了静态库动态库里面了。

§ gcc制作静态库和动态库 (Linux) - 图2

1.1 静态库的制作

§ gcc制作静态库和动态库 (Linux) - 图3 命名规则

  • lib + 库的名字 + .a
  • 例如:libMyTest.a

§ gcc制作静态库和动态库 (Linux) - 图4 制作步骤

  1. 生成对应的目标文件 .o

    1. gcc -c hello.c -o hello.o
  2. 将生成的 .o文件打包

    1. ar rcs 静态库的名字(libMyTest.a) 生成的所有的.o
  3. 发布和使用静态库

    • 发布静态库
    • 头文件

说明:

  • 通过把 .c文件(即源代码)转换成目标文件 .o后,客户就不知道核心技术的实现方式了
  • ar 是对 .o 的二进制文件进行打包,rcs 是打包参数,把所有的 .o二进制文件打包成一个 .a 文件,即静态库。因此,静态库就是一个打包了的二进制文件的集合
  • 接口 API 是在头文件中体现出来的

§ gcc制作静态库和动态库 (Linux) - 图5 下面打包一个静态库,其文件目录如下图所示

  1. Calc
  2. ├── include
  3. └── head.h
  4. ├── lib
  5. ├── main.c
  6. └── src
  7. ├── myAdd.c
  8. ├── myDiv.c
  9. ├── myMul.c
  10. └── mySub.c

说明

  • include 文件夹:存放头文件,提供给用户调用的接口API
  • lib 文件夹:存放库文件,即:生成的静态库和动态库
  • src 文件夹:存放源文件
  • main.c 程序:是用户调用 head.h头文件里面的接口,然后在调用静态库里面的二进制文件,这个二进制文件是我们实现的算法。

下面制作代码:

§ gcc制作静态库和动态库 (Linux) - 图6 头文件include/head.h里面存放的是调用的接口API

  1. #ifndef __HEAD_H_
  2. #define __HEAD_H_
  3. int myAdd(int a, int b);
  4. int mySub(int a, int b);
  5. int myMul(int a, int b);
  6. int myDiv(int a, int b);
  7. #endif

§ gcc制作静态库和动态库 (Linux) - 图7 源代码 src/myAdd.c 实现的是加法运算:

  1. #include "head.h"
  2. int myAdd(int a, int b) {
  3. int result = a + b;
  4. return result;
  5. }

§ gcc制作静态库和动态库 (Linux) - 图8 源代码 src/mySub.c 实现的是减法运算:

  1. #include "head.h"
  2. int mySub(int a, int b) {
  3. int result = a - b;
  4. return result;
  5. }

§ gcc制作静态库和动态库 (Linux) - 图9 源代码 src/myMul.c 实现的是乘法运算:

  1. #include "head.h"
  2. int myMul(int a, int b) {
  3. int result = a * b;
  4. return result;
  5. }

§ gcc制作静态库和动态库 (Linux) - 图10 源代码 src/myDiv.c 实现的是乘法运算:

  1. #include "head.h"
  2. int myDiv(int a, int b) {
  3. int result = a / b;
  4. return result;
  5. }

§ gcc制作静态库和动态库 (Linux) - 图11 main.c文件是对头文件的调用,然后调用静态文件,对算法的使用,但是并不知道算法的具体实现源码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "head.h"
  4. int main(void) {
  5. int sum = add(2, 24);
  6. printf("sum = %d\n", sum);
  7. system("pause");
  8. return 0;
  9. }

用户在 main.c 中引入头文件 #include "head.h" ,即在 ./include/head.h,就可以使用 ./include/head.h 中定义的接口 int add(int a, int b);当 main.c 程序执行到 add(int a, int b)接口时,就会到 ./src 文件夹下找静态文件(打包的二进制文件——即:加法算法的具体实现)。

接下来就把上面的文件制作出来

步骤1-2:生成目标文件,之后生成静态库文件

§ gcc制作静态库和动态库 (Linux) - 图12

步骤3:将生成的库编译文件移动到 lib 文件夹下

步骤4:使用静态库文件,编译 main.c 文件

§ gcc制作静态库和动态库 (Linux) - 图13

步骤5:检查,我的是Windows平台,是一个 .exe 程序(说明 .a 静态库文件在 Windows 平台下也是可以用的),打开后如图

§ gcc制作静态库和动态库 (Linux) - 图14

1.2 静态库相关文件查看

§ gcc制作静态库和动态库 (Linux) - 图15 使用 nm命令查看静态库

可以使用 nm命令查看静态库文件中具体打包了哪些二进制文件(即 .o 文件)

§ gcc制作静态库和动态库 (Linux) - 图16

§ gcc制作静态库和动态库 (Linux) - 图17 使用 nm命令查看生成的可执行文件

1.3 静态库的优缺点

§ gcc制作静态库和动态库 (Linux) - 图18

当我们在 main.c 函数中调用静态库的时候,因为其中封装了多个 .o文件,而 main.c 在生成可执行文件的过程中只会把静态文件中的 add.o 和 sub.o 两个文件打包到可执行文件中,静态文件中的其他没有用到的 .o文件不会被打包进可执行文件中。

静态库的优点:

  • 发布程序的时候,不需要提供对应的库了,因为库已经被打包到了可执行文件中去了。
  • 库的加载速度比较快,因为库已经被打包到可执行文件中去了。

静态库的缺点:

  • 库被打包到应用程序(最后生成的可执行文件)中,如果库很多的话就会导致应用程序的体积很大。
  • 库发生了改变,需要重新编译程序,如果源代码比较多,可能编译一遍一天就过去了。

二、gcc制作共享库(动态库)

2.1 动态库的制作

§ gcc制作静态库和动态库 (Linux) - 图19 命名规则

  • lib + 库的名字 + .so
  • 例如:libMyTest.so

§ gcc制作静态库和动态库 (Linux) - 图20 制作步骤

  1. 生成与位置无关的代码(生成与位置无关的 .o)
  2. 将 .o 打包成共享库(动态库)
  3. 发布和使用共享库

注意:

  • 静态库生成的 .o 文件是和位置有关的
  • 用 gcc 生成和位置无关的 .o 文件,需要使用参数 -fPIC-fpic

§ gcc制作静态库和动态库 (Linux) - 图21 动态库制作相关实例

在了解什么叫生成和位置无关的 .o 文件之前,我们先来了解以下虚拟地址空间。

虚拟内存是一个抽象概念,它为每个进程提供了一个假象,即每个进程都是在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间,下图所示的是Linux进程的虚拟地址空间。

§ gcc制作静态库和动态库 (Linux) - 图22

§ gcc制作静态库和动态库 (Linux) - 图23

现在,开始制作动态库。

  1. Calc
  2. ├── include
  3. └── head.h
  4. ├── lib
  5. ├── main.c
  6. └── src
  7. ├── myAdd.c
  8. ├── myDiv.c
  9. ├── myMul.c
  10. └── mySub.c

不再重复上面的代码了,下面直接开始制作动态库的流程。

步骤1:生成与位置无关的目标文件 .o

§ gcc制作静态库和动态库 (Linux) - 图24

步骤2:生成动态库文件 .so

§ gcc制作静态库和动态库 (Linux) - 图25

步骤3:将动态库文件 libMyCal.so 放到 lib 文件夹下

步骤4:编译 main.c 文件,用户有两种方法使用动态库文件

  1. # 用法1
  2. gcc main.c lib/libMyCal.so -o main -Iinclude
  3. # 用法2
  4. gcc main.c -Iinclude -L lib -l MyCal -o main

§ gcc制作静态库和动态库 (Linux) - 图26

步骤5:检查生成的 main,这里我们使用 Linux 系统,因为 Windows 平台下的动态库是 .lib + .dll,而 Linux 下的是 .so ,所以这里用 Linux 平台。

其中用法1生成的 main 程序可以正确执行,而用法2生成的程序则会产生如下错误

§ gcc制作静态库和动态库 (Linux) - 图27

2.2 动态库查找不到的解决办法

§ gcc制作静态库和动态库 (Linux) - 图28 使用 ldd 命令查看动态库链接文件

我们可以看到,上面执行可执行程序的时候,提示找不到动态库,这并不一定是动态库文件不存在,可能是由于链接不到

是不是真的链接不到,我们可以通过一个命令ldd:查看可执行文件在执行的时候,依赖的所有共享库/动态库(.so文件)

ldd命令使用:

  1. ldd 可执行文件名

现在使用 ldd 命令查看动态链接库

§ gcc制作静态库和动态库 (Linux) - 图29

  1. [root@iZbp18vd1p2tytbwn5vgaqZ StaticProject]# ldd main
  2. linux-vdso.so.1 (0x00007ffc579d7000) # 后面的数字是库的地址
  3. libMyCal.so => not found # 没有找到共享库 libMyCal.so
  4. libc.so.6 => /lib64/libc.so.6 (0x00007f850cef8000) # libc.so.6 是Linux下的标准库
  5. /lib64/ld-linux-x86-64.so.2 (0x00007f850d2bd000) # 动态链接器,本质是一个动态库

§ gcc制作静态库和动态库 (Linux) - 图30 动态链接器查找共享库文件

§ gcc制作静态库和动态库 (Linux) - 图31

如上图,可执行程序 .main 执行的时候,调用需要调用的共享库 libMyCal.so,但是实际上这个调用是通过动态链接器来调用的。动态库就是通过 动态链接库--/lib64/ld-linux-x86-64.so.2 加载到我们的可执行程序(应用程序)中的。

那么 动态链接库--/lib64/ld-linux-x86-64.so.2是通过什么规则查找可执行文件在执行时,需要的动态库文件的呢?

答:其实就是通过环境变量查找需要的动态库文件

在 Linux 下查看环境变量的方法

  1. echo $PATH

§ gcc制作静态库和动态库 (Linux) - 图32 动态库查找不到的解决办法一(不推荐使用)

把自己制作的动态库放到根目录下的系统动态库中,即 /lib目录下

  1. sudo cp ./lib/libMyCal.so /lib

从上面的结果可以看到,把自己制作的动态库拷贝到系统动态库中之后,动态链接器根据环境变量就可以找到这个动态库,然后正确加载到可执行程序中。

注意:这种方法一般不会使用的,因为如果你的动态库的名字和系统中某个动态库的名字一样,就可能会导致系统奔溃的!!!这里只是做一个演示,证明动态链接器是根据环境变量去查找要加载的动态库。
§ gcc制作静态库和动态库 (Linux) - 图33 动态库查找不到解决方法二(临时测试设置)

通过把动态库添加到动态库环境变量中,即:export LD_LIBRARY_PATH=./lib

§ gcc制作静态库和动态库 (Linux) - 图34

使用export添加环境变量,把当前动态库所在的位置(文件夹位置)添加到LD_LIBRARY_PATH变量中,可执行程序在执行的时候会在默认的动态库之前从LD_LIBARAY_PATH变量中查找有没有所需动态库。

注意:这种方法只是临时的,当我们关闭终端,下次再执行程序又会提示找不到动态库。因此,这钟方法一般是再开发动态库的过程中,用于临时的测试。

§ gcc制作静态库和动态库 (Linux) - 图35 动态库查找不到解决方法三(永久设置——不常用)

在当前用户的家目录(home)下的.bashrc文件中配置LD_LIBRARY_PAHT环境变量。

  1. cd ~
  2. vi .bashrc
  3. # 然后再最后一行添加一个环境变量,如果没有就创建(Shift+G跳到最后一行)
  4. # 然后把动态库的绝对路径赋值给该变量
  5. export LD_LIBARAY_PATH=/home/shliang/shliang/gcc_learn/Calc/lib
  6. # 保存退出,用source激活配置,如果不激活需要重启终端,因为终端每次重启都会从.bashrc中加载一次配置
  7. source .bashrc

§ gcc制作静态库和动态库 (Linux) - 图36

上面添加完环境变量之后就可以找到动态库了。

§ gcc制作静态库和动态库 (Linux) - 图37 动态库查找不到解决方法四(永久设置)

这种方法,相对与前三种复杂一些,一定要掌握,可能以后工作中用到的就是这种。做法如下:

  1. 需要找到动态连接器的配置文件:/etc/ld.so.conf
  2. 把我们自己制作的动态库目录的绝对路径写到配置文件中
  3. 更新配置文件:sudo ldconfig -v
    • ld:dynamic library 动态库的缩写
    • -v :是更细配置文件的时候输出更新信息。
  4. 修改配置文件的路径位置:/etc/ld.so.conf,把 /home/shliang/shliang/gcc_learn/Calc/lib 添加到 /etc/ld.so.conf 配置文件中

之后就可以找到动态库了

2.3 动态库的优缺点

动态库的优点

  • 执行程序的体积小:程序在执行的时候采取加载动态库,并没有和可执行程序打包在一起
  • 动态库更新了,不需要重新编译程序(不是绝对的,前提是函数的接口不变,内容便里没事)

动态库的缺点

  • 程序发布的时候,需要把动态库提供给用户
  • 动态库没有被打包到应用程序中,加载速度相对较慢

三、参考链接

版权声明:本文为CSDN博主「点亮~黑夜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_41010198/article/details/105434085