什么是库?
库是已经写好的,成熟的,可以复用的代码。程序员编程的时候都要依赖很多基础的底层库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
回顾将源程序到可执行程序的过程:
链接
链接主要完成两个任务:
- 合并目标文件。
- 符号解析与重定位。
静态链接
在链接阶段,将汇编生成的.o文件和所需要的库一起链接打包到可执行文件中去。因此称为静态链接。
一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。它有以下特点:
- 静态库对函数的链接是在编译时完成的。
- 移植性好。
- 浪费空间和资源,因为和所需要函数相关的所有的库都被打包进可执行文件了。
Linux静态库命名规范:必须是"lib[your_library_name].a"。
创建静态库的步骤:
- 首先编译成目标文件.o:
g++ -c statictest.cpp
- 再使用ar工具将目标文件打包成.a静态库文件(Linux下使用ar工具、Windows下vs使用lib.exe):
ar -crv libstatictest.a statictest.o
可以使用makefile文件(CMake等等工程管理工具)来生成静态库,更方便快速。
- 使用静态库:
g++
-L:库所在目录
-l:链接时需要的库,编译器查找时会在指定的名称前加lib,后面加上.a(静态链接)或.so(动态链接)来确定库的名称。g++ main.cpp -L../directoryOfLib -lstatictest
动态链接
静态库有什么问题?
- 浪费空间。
- 库更新以后需要重新编译:库是被复制到可执行文件中去了,如果某个库更新了,则与它相关的所有可执行文件都需要重新编译。
为了解决以上问题,可以使用动态库:
- 解决浪费空间:动态库在编译时不会被载入,而是在程序运行时才载入。此时如果不同的应用程序调用相同的库,那么在内存中只需要一份该库的实例。
- 解决全量更新问题:动态库修改后不用修改重新编译生成可执行文件,增量更新。
动态库的特点:
- 动态库只有一份,可以实现进程之间的资源共享。(因此动态库也称为共享库)
- 对库函数的链接推迟到程序运行时。
- 程序升级变得简单。
- 可以在链接载入时完全由程序员在程序代码中控制。
Linux动态库命名规范:必须是"lib[your_library_name].so"。
每个共享库都有个特殊的名字"soname"。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
在文件系统中,"soname"是一个链接。对于每个动态库来说,对于编译器来说,有一个指向实际库镜像文件 - “lib[soname].so”。
创建动态库的步骤:
-
首先编译成目标文件.o,此时要加编译器选项-fPIC(position independent code):
g++ -fPIC -c dynamictest.cpp
-fPIC ,产生与位置无关代码(Position-IndependentCode),作用于编译阶段,使产生的代码中只有相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
-
生成动态库,-shared:
g++ -shared -o libdynamictest.so dynamictest.o
-
使用动态库(同静态库):
g++ main.cpp -L../directoryOfLib -ldynamictest
如何告诉系统我创建了一个动态库?或者说动态库是如何定位库的?
系统加载程序的时候,知道了所需依赖库的名称。那么它如何寻找呢?此时就需要系统动态载入器了"dynamiclinker/loader"。
对于elf格式的可执行文件,是由ld-linux.so*来完成的。它先后搜索elf文件的DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib 目录找到库文件后将其载入内存。
所以,如何让系统找到库?
- 放在安装在/lib或者/usr/lib下。
- 将目录添加到/etc/ld.so.cache文件中:
- 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径。
- 运行ldconfig ,该命令会重建/etc/ld.so.cache文件。