目录
1、制作静态库
1.1、ar命令
1.1.1、使用makefile制作静态库
1.2、如何发布自己的静态库给别人呢?
1.3、如何使用静态库呢?
1.3.1、前言(搜索优先级)
1.3.2、方法一
1.3.3、方法二
2、制作动态库
2.1、使用动态库遇到的问题
2.1.1、上面的问题如何解决呢?(如何使用动态库)
3、链接动态库和静态库的顺序
4,为什么要有库?
问题:如果得到别人的.o文件,我们是可以使用这个文件里的方法的,但如果.o文件过多,比如有1000个,那Linux中使用gcc命令链接这些二进制文件的时候岂不是要将1000个文件的名称都写出来?更何况文件多了之后还容易漏掉个别文件,所以这种方法不可取,那该怎么办呢?
答案:使用ar命令将这些.o文件打包(正式的说法叫归档),这个归档的过程就叫制作静态库。将所有 .o文件(即目标二进制文件)归档即可制作一个静态库出来。可以得到一个结论,静态库中全是.o文件,没有.h文件。
注意事项:
1.归档后的静态库的名称前3个字符必须是lib,文件后缀名必须是.a,其他的自定义即可,如下图的libhello.a。
2. ar -rc,这里的r表示replace(替换),c表示create(创建)。
创建一个发布目录文件,里面包含目录文件include和目录文件lib。include文件里包含了库的所有头文件,lib文件中包含了所有的静态库文件,如下图中的 libhello.a文件,并且因为 lib目录中包含了静态库文件,那么一堆.o文件也就不需要了。最后将这个发布目录文件交给别人,这就是发布的过程。注意只要是库,头文件也必须给别人,不然谁知道可以使用哪些方法,所以发布目录文件里存在include目录。
如下图就是模拟发布的过程(创建发布目录文件的过程)
代码如下
运行结果如下
Linux默认头文件的搜索顺序
1.如果源文件中使用双引号来包含头文件,则OS首先在源文件所在的当前目录中查找头文件。
2.如果gcc编译时使用-I /xxx/xxx,OS则在其中查找。
3.如果设置了环境变量C_INCLUDE_PATH,则系统在指定的目录中查找。
4.最后在系统设置的默认路径中查找,编译时一定会在该路径中搜索所需的头文件。
Linux默认库文件的搜索顺序
1.如果gcc编译时使用 -L /xxx/xx,OS则在其中查找。
2.如果设置了环境变量LD_LIBRARY_PATH,则系统在指定的目录中查找。
3.最后在系统的库文件默认搜索路径中查找。
1.首先将include目录的所有文件拷贝到系统寻找头文件的默认路径中,如下图。
2.然后将静态库文件拷贝到系统寻找静态库文件的默认路径中,如下图。
遇到的问题:经过前面的步骤后,使用gcc编译下面代码失败,为什么呢?
代码如下
运行结果如下
答案:libhello.a这个静态库不在系统寻找静态库的默认路径中,也不是语言提供的,是我们自己编写并新增到OS中的,所以属于第三方库。如果libhello.a是系统自带,那么libhello.a肯定在系统寻找静态库的默认路径中,此时自然可以通过编译并运行,如果是C语言提供的静态库,由于gcc是C语言编写的,编译时会自动帮我们链接C语言提供的静态库,所以上图代码也可以通过编译并运行。所以说到这里,结果已经显而易见,编译失败是因为没有告诉gcc链接哪个静态库,所以都没有链接到libhello.a,此时使用静态库libhello.a里的方法自然是不行的。那么如何链接libhello.a呢?请往下看。
3.然后链接想要使用的静态库。如下图,gcc后面加上 -l 选项和静态库的名称。注意这里文件的名称需要去掉前缀 lib 和后缀 .a才行,如下图将静态库libhello.a拆解成了hello。
4.最后成功地使用了静态库libhello.a编译出可执行文件a.out。(由于编译时没有带生成文件的名称,所以生成文件的名称为a.out)
上面的操作将我们自己写的库拷贝到了系统搜索库的默认路径中,这就叫做库的安装。最好不要将自己写的库拷贝进OS寻找库的默认路径中,会造成系统的污染,所以想要使用静态库还有更好的方法。
直接在编译时带上头文件所在的路径和静态库所在的路径即可,如下图。 注意 -I(为大写的i,不是小写的L)表示include,-L表示lib(library)。
错误示例:
1.下图红框处没有带具体的静态库的名称,所以编译出错。因为./hello/lib路径下可能有很多个静态库文件,如不加入具体的名称,会不知道链接哪一个。
3.gcc在编译时,根据上文的搜索文件的顺序,编译器搜索头文件和静态库所在的路径的顺序先是当前路径,但此时当前路径中不存在main.c里包的头文件,也不存在包含main.c里使用的方法的静态库,所以编译失败。
1.首先生成若干个需要的 .o 文件,注意有选项-fPIC,如下图。
2.然后生成动态库即可,注意有选项-shared,如果没有这个选项,编译器则会认为你是要生成可执行文件,而不是动态库,然后生成失败,因为我们写的动态库文件中没有main函数,生成不了可执行文件。
如下图是使用makefile同时编译动态库和静态库
问题:首先创建一个包含动态库的发布目录文件,如上图的output文件。由于gcc默认采用动态链接,所以当动态库和静态库同名并同时存在时,gcc会链接动态库。gcc编译成功后生成了下图的可执行文件a.out,当执行a.out的时候会发生报错,错误信息是找不到该动态库,所以无法将动态库加载进内存,为什么会这样呢?
答案:动态库libhello.so是一个独立的文件,和可执行文件a.out是通过分批加载的方式加载进内存,所以运行失败的原因就是此时动态库libhello.so没有被加载进内存,这又是为什么呢?因为我们只在用gcc编译的时候告诉了编译器链接libhello.a这个动态库文件,但并没有告诉操作系统应该加载哪个动态库进内存。
问题:为什么运行链接静态库的可执行程序时不会出现上述的问题呢?
答案:因为静态库的代码是以拷贝的形式填充进了可执行程序,所以不会分批加载,所以不会产生上述问题。
1.方法一
和使用静态库的方法一相同,直接将动态库文件和头文件添加到系统搜索库文件和头文件的默认路径中,之后运行可执行文件a.out时,需要的动态库文件自然就可以被系统找到。依然不建议使用此方法,容易污染系统的生态。
2.方法二
修改环境变量LD_LIBRARY_PATH,LD表示load(加载),LIBRARY表示lib(库)。
如上图,在冒号( : )的后面加入库所在的路径即可,路径最后不需要带库本身的名称。注意不要将之前的环境变量覆盖掉了,所以上图中等于号(=)的后面是 $LD_LIBRARY_PATH:,而不是直接的一条路径。
此种方式有一个缺点:环境变量是内存级的变量,所以每次登录时,环境变量都会从某个配置文件中读取数据,所以退出登录后再次登录,之前用户在shell中设置的环境变量就失效了。
3.方法三
改进方法二的弊端,本种方法就是修改配置文件。如果觉得自己写的库很重要,但又不想加载到系统搜索库文件的默认路径中,防止污染系统的生态,就可以使用本种方法。
首先在上图的路径中创建一个普通文件,文件名无所谓,但后缀名必须是.conf,如下图。
然后用vim编辑器,将动态库的路径写进105.conf,注意路径中不需要动态库本身的名称。(前面不一定非要使用vim编辑器,只要你能把路径写进配置文件就行)
配置文件编写完毕后,最后在shell命令行中使用ldconfig指令,让配置文件105.conf生效即可,如下图。注意修改配置文件一般需要较高权限,所以加sudo。
上面操作完成后,此时就算环境变量LD_LIBRARY_PATH中不存在动态库所在的路径,也可以正常运行可执行程序。
容易陷入的误区:
有时候发现删除了配置文件105.conf,但需要105.conf文件里写的路径上的动态库的可执行程序依然可以正常运行,这是因为有缓存的存在,此时再次sudo ldconfig将缓存更新,需要对应动态库的可执行程序就运行不了了。
4.方法四
软连接方案,感兴趣自行探索。
情景1如下
如上图,当动态库和静态库同时存在并且同名时,gcc编译时默认使用动态库。
情景2如下
如上图,当动态库和静态库同时存在并且同名时,如果非要使用静态库进行链接,那么必须加-static选项,-static表示摒弃编译器默认优先使用动态库的原则,并直接使用静态库。
如上图,即使编译器不带-static选项进行编译时默认链接动态库,但当只有静态库时,也只能以静态链接的方式将静态库拷贝进程序中,程序需要的其它库文件如果是动态库,则以动态方式链接。此时发现ldd命令列出的动态库依赖关系中并没有hello这个静态库文件,事实上不必担心,a.out这个文件一定使用了hello这个静态库的,因为a.out可以成功运行。
因为简单便利和安全。安全是指如果想把方法给别人使用,但不想泄漏代码,就可以通过只把库给别人。