Linux的动态库和静态库
文章目录
- 前言
- 动静态库概念补充
- 制作静态库
- 静态库的打包
- 链接静态库
- 制作动态库
- 动态库的打包
- 链接动态库
- 加载动态库
前言
如果我们想把我们的方法给别人用,有什么办法呢?
- 把源文件直接给他
- 把我们的源代码打包成库(头文件+库)。
在过去学习gcc/g++工具的时候简单初步介绍了一下它们的概念:Linux开发工具的使用(二):gcc/g++与make/makefile,相似内容不再过多赘述,本文重心将放在库的制作与使用上。
动静态库概念补充
我们知道,库是由一个或多个被编译好的.o文件打包而成的,也就是说库与我们写好的.o文件一同链接为可执行程序的话,库的源代码就也是我们程序的一部分了。
库的命名规则
库分为静态库和动态库,它们的命名有所区别:
- 动态库:
libXXX.so
,lib
为前缀,.so
为后缀 - 静态库:
libXXX.a
,lib
为前缀,.a
为后缀
例如libstdc++.so.6,去掉前缀跟后缀,最终库名为 stdc++。
库的搜索路径:
- 由系统指定的目录
Linux系统中,库文件和头文件分布在多个标准目录中。
库文件:
/usr/lib/
- 32位系统的主要库文件路径/usr/lib64/
- 64位系统的主要库文件路径/lib/
- 系统核心库/lib64/
- 64位系统核心库/usr/local/lib/
- 本地安装软件的库文件/opt/
下某些软件的库文件
头文件:
/usr/include/
- 系统主要头文件/usr/local/include/
- 本地安装软件的头文件- 各个软件包特定的include目录
- 由环境变量指定的目录 (
LIBRARY_PATH
)
后文会讲 - 从左到右搜索
-L
指定的目录(链接时gcc的选项)。
后文会讲
注意:
- 系统提供的库和编译器提供的库为前两方库,其他的库被称为第三方库
- 如果系统只提供了静态库,则gcc就只能对该库进行静态链接
- 如果系统需要链接多个库,gcc可以链接多个的库
制作静态库
我们编写一段具有简单计算功能的代码,添加头文件,对他们进行打包
mymath.c
#include "mymath.h"int myerrno = 0;int add(int a, int b)
{return a + b;
}int sub(int a, int b)
{return a - b;
}int mul(int a, int b)
{return a*b;
}int dev(int a, int b)
{if(b == 0){myerrno = 1;return -1;}return a/b;
}
mymath.h
#pragma once
#include <stdio.h>
extern int myerrno;int add(int a, int b);int sub(int a, int b);int mul(int a, int b);int dev(int a, int b);
main.c
#include "mymath.h"int main()
{//使用静态库int n = dev(10, 0);printf("10/0 = %d, errno = %d", n, myerrno);return 0;
}
静态库的打包
具体步骤如下:
- 先编译源文件
mymath.c
为mymath.o
二进制文件
gcc -c mymath.c
- 用ar命令将.o文件打包为.a文件
ar -rc libmymath.a mymath.o
- ar 是 GNU 提供的归档工具,常用来将目标文件打包为静态库。
- 我们还可以使用 ar 反向查看静态库中的具体文件
ar -tv 静态库文件
-rc
意指replace and create
实践中,我们通常使用makefile脚本来快速打包:
static-lib = libmymath.a$(static-lib):mymath.oar -rc $@ $^mymath.o:mymath.cgcc -c $^.PHNOY:clean
clean:rm -rf *.o *.a mylib .PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/lib
可以看到,我们通过make output还可以将头文件和库文件分门别类的拷贝一份在各自的目录里。
链接静态库
现在我们有了自己的静态库,接下来要考虑如何将它与main.c一并链接生成可执行程序了。
链接命令如下:
gcc main.c -I ./mylib/include -L ./mylib/lib -lmymath
命令解读:
1. -I [头文件路径]
:前面说过,系统只会在系统默认的路径下查找头文件,因此我们头文件放在哪系统是不知道的,需要用这个选项来为系统指明位置。
2. -L [库文件路径]:与头文件类似,需要用这个选项来为系统指明库文件位置。
3. -l+库名
:我们指明的只是存放库文件的目录,系统并不知道我们需要链接哪些库文件,因此需要指明具体库文件。
注意:
-l
与库名需要紧贴,不能用空格隔开-l
选项几乎是必用的-I
和-L
使用起来非常不爽,可以通过“安装该库”或者在系统路径下建立软链接来避免使用。
安装库文件:
原理很简单,将我们制作的库文件和头文件拷贝一份到系统默认路径下即可
sudo cp mylib/include/mymath.h /usr/include
sudo cp mylib/lib/libmymath.a /lib64/
注意:
- 系统路径在root用户下,需要sudo提权
- 记得指明需要拷贝的具体文件
- 安装好后,就只需用-l选项指明库文件就行了。
gcc main.c -lmymath
制作动态库
我们编写myprint.c和mylog.c方法,打印一些提示符即可。并添加相应头文件,对他们进行打包
myprint.c
#include "myprint.h"void Print()
{printf("hello linux!\n");printf("hello linux!\n");printf("hello linux!\n");printf("hello linux!\n");
}
myprint.h
#pragma once#include <stdio.h>void Print();
mylog.c
#include "mylog.h"void Log()
{printf("it is log\n");printf("it is log\n");printf("it is log\n");printf("it is log\n");
}
mylog.h
#pragma once#include <stdio.h>void Log();
main.c
#include "mymath.h"
#include "mylog.h"
#include "myprint.h"int main()
{//使用静态库int n = dev(10, 0);printf("10/0 = %d, errno = %d", n, myerrno);//使用动态库Log();Print();return 0;
}
动态库的打包
动态库的打包不像静态库一样,需要借助ar工具,他就像是gcc的亲儿子一样,gcc天然的具有动态库的打包功能,具体如下:
gcc -shared -o libmymethod.so *.o
-shared
: 表示生成共享库格式-fPIC
:需要打包成动态库的源文件,编译时必须使用-fPIC选项,它表示产生位置无关码(position independent code)
比如这里的:
gcc -fPIC -c myprint.c
gcc -fPIC -c mylog.c
一样的,推荐使用makefile,我们将前面的静态库也一同打包:
dy-lib = libmymethod.so
static-lib = libmymath.a.PHNOY:all
all:$(dy-lib) $(static-lib)$(dy-lib):myprint.o mylog.ogcc -shared -o $@ $^$(static-lib):mymath.oar -rc $@ $^mymath.o:mymath.cgcc -c $^myprint.o:myprint.cgcc -fPIC -c $^mylog.o:mylog.cgcc -fPIC -c $^.PHNOY:clean
clean:rm -rf *.o *.a *.so mylib .PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/libcp *.so mylib/lib
注意这里需要完成两个任务,需要用all将二者关系独立开来。
打包后效果如下:
链接动态库
和静态链接一样,使用gcc编译main.c生成可执行程序时,需要用-I选项指定头文件搜索路径,用-L选项指定库文件搜索路径,最后用-l选项指明需要链接库文件路径下的哪一个库。
gcc main.c -I ./mylib/include -L ./mylib/lib -lmymethod -lmymath
但是与静态库的使用不同的是,此时我们生成的可执行程序却并不能直接运行。
为什么我们表明了路径还是不行呢?
可以使用ldd
命令查看动态库链接情况:
ldd a.out
原因就是我们只是告诉了gcc这个编译器,并没有告诉系统。
而将动态库告诉系统的操作就是加载。
加载动态库
常见的加载动态库的方式有四种:
- 安装动态库到系统库:拷贝到系统默认的库路径
/usr/lib64/
- 软链接动态库到系统:在系统默认的库路径
/usr/lib64/
下建立软链接 - 将自制的库文件路径添加到系统的环境变量
LD_LIBRARY_PATH
中 - 在
/etc/ld.so.conf.d
建立自己的动态库路径的配置文件,然后重新ldconfig
即可。
注意:实际情况下,我们使用的库一般都是别人成熟的库,都采用直接安装到系统的方式。
这里,我们也采用安装到系统的方式:
sudo cp mylib/include/myprint.h /usr/include/
sudo cp mylib/include/mylog.h /usr/include/
sudo cp mylib/include/mymath.h /usr/include/sudo cp mylib/lib/libmymethod.so /lib64/
sudo cp mylib/lib/libmymath.a /lib64/
值的一提的是:并不推荐将自己写的头文件和库文件拷贝到系统路径下,这样做会对系统文件造成污染,因此方便并不一定完美。
你有没有想过动态库是如何加载到程序的呢?
下篇文章我会结合这个问题再次谈谈进程地址空间的概念。
有错误欢迎指出,万分感谢
创作不易,三连支持一下吧~
不见不散!