根据链接时期不同,库分为静态库和动态库。静态库是在程序编译时链接的,动态库是在程序运行时链接的。
- 静态库是目标文件的集合,调用静态库就是把静态库中的二进制指令拷贝到可执行文件中。
- 共享库就是带入口的可执行文件,调用共享库其实就是跳转到共享库所在的二进制指令所在的位置,当可执行文件执行时,共享库会被一起加载到内存中。
一、gcc制作静态库
当你和别人做项目合作,你不可能直接把源代码给别人,那样别人就可以自己开发,因为源代码就是你的核心技术。你不应该卖给他源代码,而是应该是程序,这样你就可以根据他有什么需求进行改或添加什么功能模块等,就可以改一次就可以收费一次,这样就可以有一个长期合作。
那应该给到客户的是什么呢?
- 生成的库
- 头文件
这样把生成的库
和头文件
给客户也能够使用,只是他不知道里面具体怎么实现的。这样二者才能维持一个长期的合作
。
头文件对应的.c文件
都被打包到了静态库
和动态库
里面了。
1.1 静态库的制作
命名规则
- lib + 库的名字 + .a
- 例如:libMyTest.a
制作步骤
生成对应的目标文件
.o
gcc -c hello.c -o hello.o
将生成的
.o
文件打包ar rcs 静态库的名字(libMyTest.a) 生成的所有的.o
发布和使用静态库
- 发布静态库
- 头文件
说明:
- 通过把
.c
文件(即源代码)转换成目标文件.o
后,客户就不知道核心技术的实现方式了 ar
是对.o
的二进制文件进行打包,rcs
是打包参数,把所有的.o
二进制文件打包成一个.a
文件,即静态库。因此,静态库就是一个打包了的二进制文件的集合。- 接口
API
是在头文件中体现出来的
下面打包一个静态库,其文件目录如下图所示
Calc
├── include
│ └── head.h
├── lib
├── main.c
└── src
├── myAdd.c
├── myDiv.c
├── myMul.c
└── mySub.c
说明:
- include 文件夹:存放头文件,提供给用户调用的接口API
- lib 文件夹:存放库文件,即:生成的静态库和动态库
- src 文件夹:存放源文件
- main.c 程序:是用户调用
head.h
头文件里面的接口,然后在调用静态库里面的二进制文件,这个二进制文件是我们实现的算法。
下面制作代码:
头文件
include/head.h
里面存放的是调用的接口API
#ifndef __HEAD_H_
#define __HEAD_H_
int myAdd(int a, int b);
int mySub(int a, int b);
int myMul(int a, int b);
int myDiv(int a, int b);
#endif
源代码
src/myAdd.c
实现的是加法运算:
#include "head.h"
int myAdd(int a, int b) {
int result = a + b;
return result;
}
源代码
src/mySub.c
实现的是减法运算:
#include "head.h"
int mySub(int a, int b) {
int result = a - b;
return result;
}
源代码
src/myMul.c
实现的是乘法运算:
#include "head.h"
int myMul(int a, int b) {
int result = a * b;
return result;
}
源代码
src/myDiv.c
实现的是乘法运算:
#include "head.h"
int myDiv(int a, int b) {
int result = a / b;
return result;
}
main.c
文件是对头文件的调用,然后调用静态文件,对算法的使用,但是并不知道算法的具体实现源码
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
int main(void) {
int sum = add(2, 24);
printf("sum = %d\n", sum);
system("pause");
return 0;
}
用户在 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:生成目标文件,之后生成静态库文件
步骤3:将生成的库编译文件移动到 lib 文件夹下
步骤4:使用静态库文件,编译 main.c 文件
步骤5:检查,我的是Windows平台,是一个 .exe 程序(说明 .a 静态库文件在 Windows 平台下也是可以用的),打开后如图
1.2 静态库相关文件查看
使用
nm
命令查看静态库
可以使用 nm
命令查看静态库文件中具体打包了哪些二进制文件(即 .o 文件)
使用
nm
命令查看生成的可执行文件
1.3 静态库的优缺点
当我们在 main.c 函数中调用静态库的时候,因为其中封装了多个 .o
文件,而 main.c 在生成可执行文件的过程中只会把静态文件中的 add.o 和 sub.o 两个文件打包到可执行文件中,静态文件中的其他没有用到的 .o文件不会被打包进可执行文件中。
静态库的优点:
- 发布程序的时候,不需要提供对应的库了,因为库已经被打包到了可执行文件中去了。
- 库的加载速度比较快,因为库已经被打包到可执行文件中去了。
静态库的缺点:
- 库被打包到应用程序(最后生成的可执行文件)中,如果库很多的话就会导致应用程序的体积很大。
- 库发生了改变,需要重新编译程序,如果源代码比较多,可能编译一遍一天就过去了。
二、gcc制作共享库(动态库)
2.1 动态库的制作
命名规则
- lib + 库的名字 + .so
- 例如:libMyTest.so
制作步骤
- 生成与位置无关的代码(生成与位置无关的 .o)
- 将 .o 打包成共享库(动态库)
- 发布和使用共享库
注意:
- 静态库生成的 .o 文件是和位置有关的
- 用 gcc 生成和位置无关的 .o 文件,需要使用参数
-fPIC
或-fpic
动态库制作相关实例
在了解什么叫生成和位置无关的 .o 文件之前,我们先来了解以下虚拟地址空间。
虚拟内存是一个抽象概念,它为每个进程提供了一个假象,即每个进程都是在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间,下图所示的是Linux进程的虚拟地址空间。
现在,开始制作动态库。
Calc
├── include
│ └── head.h
├── lib
├── main.c
└── src
├── myAdd.c
├── myDiv.c
├── myMul.c
└── mySub.c
不再重复上面的代码了,下面直接开始制作动态库的流程。
步骤1:生成与位置无关的目标文件 .o
步骤2:生成动态库文件 .so
步骤3:将动态库文件 libMyCal.so 放到 lib 文件夹下
步骤4:编译 main.c 文件,用户有两种方法使用动态库文件
# 用法1
gcc main.c lib/libMyCal.so -o main -Iinclude
# 用法2
gcc main.c -Iinclude -L lib -l MyCal -o main
步骤5:检查生成的 main,这里我们使用 Linux 系统,因为 Windows 平台下的动态库是 .lib + .dll,而 Linux 下的是 .so ,所以这里用 Linux 平台。
其中用法1生成的 main 程序可以正确执行,而用法2生成的程序则会产生如下错误
2.2 动态库查找不到的解决办法
使用
ldd
命令查看动态库链接文件
我们可以看到,上面执行可执行程序的时候,提示找不到动态库,这并不一定是动态库文件不存在,可能是由于链接不到
是不是真的链接不到,我们可以通过一个命令ldd
:查看可执行文件在执行的时候,依赖的所有共享库/动态库(.so
文件)
ldd
命令使用:
ldd 可执行文件名
现在使用
ldd
命令查看动态链接库
[root@iZbp18vd1p2tytbwn5vgaqZ StaticProject]# ldd main
linux-vdso.so.1 (0x00007ffc579d7000) # 后面的数字是库的地址
libMyCal.so => not found # 没有找到共享库 libMyCal.so
libc.so.6 => /lib64/libc.so.6 (0x00007f850cef8000) # libc.so.6 是Linux下的标准库
/lib64/ld-linux-x86-64.so.2 (0x00007f850d2bd000) # 动态链接器,本质是一个动态库
动态链接器查找共享库文件
如上图,可执行程序 .main
执行的时候,调用需要调用的共享库 libMyCal.so,但是实际上这个调用是通过动态链接器来调用的。动态库就是通过 动态链接库--/lib64/ld-linux-x86-64.so.2
加载到我们的可执行程序(应用程序)中的。
那么 动态链接库--/lib64/ld-linux-x86-64.so.2
是通过什么规则查找可执行文件在执行时,需要的动态库文件的呢?
答:其实就是通过环境变量查找需要的动态库文件
在 Linux 下查看环境变量的方法
echo $PATH
动态库查找不到的解决办法一(不推荐使用)
把自己制作的动态库放到根目录下的系统动态库中,即 /lib目录下
sudo cp ./lib/libMyCal.so /lib
从上面的结果可以看到,把自己制作的动态库拷贝到系统动态库中之后,动态链接器根据环境变量就可以找到这个动态库,然后正确加载到可执行程序中。
注意:这种方法一般不会使用的,因为如果你的动态库的名字和系统中某个动态库的名字一样,就可能会导致系统奔溃的!!!这里只是做一个演示,证明动态链接器是根据环境变量去查找要加载的动态库。 动态库查找不到解决方法二(临时测试设置)
通过把动态库添加到动态库环境变量中,即:export LD_LIBRARY_PATH=./lib
使用export
添加环境变量,把当前动态库所在的位置(文件夹位置)添加到LD_LIBRARY_PATH
变量中,可执行程序在执行的时候会在默认的动态库之前从LD_LIBARAY_PATH
变量中查找有没有所需动态库。
注意:这种方法只是临时的,当我们关闭终端,下次再执行程序又会提示找不到动态库。因此,这钟方法一般是再开发动态库的过程中,用于临时的测试。
动态库查找不到解决方法三(永久设置——不常用)
在当前用户的家目录(home
)下的.bashrc
文件中配置LD_LIBRARY_PAHT
环境变量。
cd ~
vi .bashrc
# 然后再最后一行添加一个环境变量,如果没有就创建(Shift+G跳到最后一行)
# 然后把动态库的绝对路径赋值给该变量
export LD_LIBARAY_PATH=/home/shliang/shliang/gcc_learn/Calc/lib
# 保存退出,用source激活配置,如果不激活需要重启终端,因为终端每次重启都会从.bashrc中加载一次配置
source .bashrc
上面添加完环境变量之后就可以找到动态库了。
动态库查找不到解决方法四(永久设置)
这种方法,相对与前三种复杂一些,一定要掌握,可能以后工作中用到的就是这种。做法如下:
- 需要找到动态连接器的配置文件:/etc/ld.so.conf
- 把我们自己制作的动态库目录的绝对路径写到配置文件中
- 更新配置文件:
sudo ldconfig -v
- ld:dynamic library 动态库的缩写
- -v :是更细配置文件的时候输出更新信息。
- 修改配置文件的路径位置:/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