了解库:
关于库相比大家之前肯定使用过,比如C/C++里面的标准库,STL里面的各种库,我们在调用STL里的容器时都需要使用库,那么库到底是什么呢?
库的本质就是可执行程序的"半成品"
我们先来回顾一下代码的执行流程:
1.预处理: 头文件展开,去注释,宏替换,条件编译,最终形成xxx.i的文件。
2.编译: 完成词法分析、语法分析、语义分析、符号汇总等,检查无误后将代码翻译成汇编指令,最终形成XXX.s文件。
3.汇编: 将汇编指令转换成二进制指令,最终形成xxx.o
文件。
4.链接: 将生成的各个xxx.o
文件进行链接,最终形成可执行程序。
现在我们有这样的场景,有test1.c,test2.c,test3.c 最终经过上述的一系列处理转化成了test1.o,test2.o,test3.o,然后将这3个文件链接形成一个可执行程序a.out:
现在我想把我写的这个功能分享给别人,让别人能够使用test*.c中的方法,但是我不想让别人看到.c中的源代码,所以我们就可以,将test*.o打包成库,然后将库和头文件(方法的使用手册),发给别人,别人拿到库和头文件,查看头文件里每个方法的使用进行使用.o里的方法。
实际上,库的本质是一堆目标文件的集合(xxx.o)的集合,里面没用main函数但存在很多课调用的方法。
认识动静态库:
我们在liunx下见一见库吧,现在我创建test1.c,编写如下程序:
这是一个非常简单的c语言程序,编译运行一下:
注意我们调用了printf函数,但我们并没有写printf函数的实现,为什么能输出结果呢?主要原因是编译器将c语言的c标准库链接进来了,c标准库里已经写好了printf的函数实现。
查看文件链接的标准库:ldd
这就是链接的c标准库,我们查一下这个文件libc.so.6:
没错,libc.so.6是一个软链接文件,我们再来查看一下这个目标文件的文件类型,使用file命令:
可以发现它是一个共享的库,简单来说就是动态库。
- 在Linux当中,以
.so
为后缀的是动态库,以.a
为后缀的是静态库。- 在Windows当中,以
.dll
为后缀的是动态库,以.lib
为后缀的是静态库。
认识了动态库,那静态库,又是什么呢?
动态库是和目标文件链接,具体怎么链接下面会讲,而静态库确不同,静态库是在编译的时候,将库中的代码直接拷贝到目标文件中,这就导致了我们最终形成的目标文件会很大,但优势在于形成了可执行程序后,该可执行程序可独立运行,不再需要库,但动态库不行,在日常我们都会使用动态库,很少使用静态库。
写一个自己的库:
在认识完库后,我们其实现在已经完全可以自己写一个库,我们简单实现一下加法:
test2.c:
test3.c:
test2.h:
test3.h:
我们先编程test* .c 形成test*.o:
使用ar命令将目标文件打包成静态库,下面我们使用ar
命令的-r
选项和-c
选项进行打包。
-r
(replace):若静态库文件当中的目标文件有更新,则用新的目标文件替换旧的目标文件。-c
(create):建立静态库文件。
再打包一个静态库,改后缀即可:
还可以使用ar命令中的选项查看库中的文件的信息:
-t
:列出库中的文件。-v
(verbose):显示详细的信息。
当我们把库给别人使用时,只需两个文件,一个存放头文件,一个存放库,所以我将所以头文件放入include目录下,两个库放在lib下,再将这两个目录放在mylib下:
使用:
现在我们就相当于这个库的使用者,我们编写main函数来使用Add和Sub函数:
这里我们不能直接用gcc像以前一样编译main.c,因为现在gcc编译器默认只认系统提供的库,而我们需要链接的是第三方库,这里我们链接第三方库有这几种方法如下:
第一种方法我们给编译器加上一些选项(推荐):
-I
:指定头文件搜索路径。-L
:指定库文件搜索路径。-l
:指明需要链接库文件路径下的哪一个库。
注意-l后面接库名时需要去掉库的前缀lib和库的后缀.so或者.a
gcc main.c -I ./mylib/include/ -L ./mylib/lib/ -lmyc
运行一下:
第二种方法:将头文件和库拷贝到系统默认的库路径
前提我们需要知道系统默认的头文件和库文件路径:
系统头文件: /usr/include
系统库文件: /usr/lib64
将我们的库和头文件拷贝到对应目录就行,这里不做演示,因为这种方法不是很推荐,因为我们应尽量避免对系统默认文件修改。
注意上面我打包了两个库,虽然看后缀一个静态库,一个动态库,但是其实上述的库都是静态库,linux不是单单通过后缀来判断一个库的类型,下面就来看看如何真正打包一个动态库吧。
动态库打包:
还在之前的几个目标文件:
用gcc编译时带上-fPIC选项:
-
-fPIC
(position independent code):产生位置无关码。gcc -fPIC -c test2.c test3.c
-fPIC
作用于编译阶段,告诉编译器产生与位置无关的代码,此时产生的代码中没有绝对地址,全部都使用相对地址,从而代码可以被加载器加载到内存的任意位置都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
打包:使用-shared选项将所有目标文件打包为动态库
与生成静态库不同的是,生成动态库时我们不必使用ar命令,我们只需使用gcc的-shared选项即可。
gcc -shared -o libmyc.so test2.o test3.o
像之前一样将头文件和动态库组合进一个文件夹:
该动态库和刚才的静态库使用方法一样:
gcc main.c -o a.out -I ./libmyc/include/ -L ./libmyc/lib/ -lmyc
需要注意的是,我们使用-I
,-L
,-l
这三个选项都是在编译期间告诉编译器我们使用的头文件和库文件在哪里以及是谁,但是当生成的可执行程序生成后就与编译器没有关系了,此后该可执行程序运行起来后,操作系统找不到该可执行程序所依赖的动态库,我们可以使用ldd
命令进行查看。
我们只是告诉了编译器头文件和动态库的位置,编程成可执行程序运行后变成进程,就和编译器无关了,就变成了一个进程,进程被操作系统管理,此时操作系统还不知道头文件和动态库的位置。
解决方法如下:
方法一: 拷贝.so文件到系统共享库路径下
cp ./libmyc/lib/libmyc.so /usr/lib64/
注意系统共享库路径,系统不同可能路径名有差异,需自行百度
这时我们再运行a.out:
方法二:更改
LD_LIBRARY_PATH
LD_LIBRARY_PATH
是程序运行动态查找库时所要搜索的路径,我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH
环境变量当中即可。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/testdir/libmyc/lib
此方法在ubuntu 20.4版本已失效,故不作演示,centos的小伙伴可以自己去试一下。
方法三: 配置
/etc/ld.so.conf.d/
contos:
/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件,而这些配置文件当中存放的都是路径,系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径,之后就会在每个路径下查找你所需要的库。所以我们只需将动态库路径放在该配置文件中,当程序执行后,系统就会通过该配置文件找到所需的库。
先来看看/etc/ld.so.conf.d/
首先将库文件所在目录的路径存入一个以.conf为后缀的文件当中。
再将这个.conf文件放入/etc/ld.so.conf.d中:
此时a.out还是无法链接动态库的,因为配置文件只在系统刚启动的时候会更新生效给我们的系统配置好,中途修改无法马上修改,所以我们用ldconfig命令更新一下配置文件:
ldconfig
最终可运行a.out,经过我的实测,unbuntu貌似无法使用该方法,不过unbuntu还有别的方法
unbuntu:
在/etc/ld.so.conf后面直接追加新第三方动态库路径:
nano /etc/ld.so.conf
同样改完后ldconfig使配置文件生效。最后运行a.out