Linux共享库、静态库、动态库
引言
程序函数库可分为3种类型:静态函数库(static libraries)、共享函数库(shared libraries)、动态加载函数库(dynamically loaded libraries)。
-
静态函数库,是在程序执行前就加入到目标程序中去了,在Linux中后缀名是
.a
,windows上后缀名是.lib
; -
动态函数库同共享函数库是一个东西(在linux上叫共享对象库, 文件后缀是
.so
,windows上叫动态加载函数库, 文件后缀是.dll
)
Linux中命名系统中共享库的规则.
程序编译过程:
1.静态库
在链接阶段,将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件(.a)就是静态库。
静态库特点:
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
. 空间浪费:静态库参与形成的每个可执行程序将静态库拷贝,并加载进内存,即每个用到该静态库的进程会单独拥有一份该静态库的拷贝。同时,静态库生成的时候,会将其调用的所有的库打包在一起,而动态库却是只将自身的代码生成库文件。因此,相同代码生成的静态库本来就比动态库大,再加上调用时被拷贝多份,自然会浪费很多内存空间。
另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库libhelloworld.a更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
1.1静态库生成
假设有三个源文件helloworld_1.c helloworld_2.c helloworld.h
gcc -c helloworld_1.c helloworld_2.c #生成helloworld_1.o helloworld_2.o
ar crv libhelloworld.a helloworld_1.o helloworld_2.o
ar 命令用来生成静态库 ,crv 是命令选项:
- c 如果需要生成新的库文件,不要警告
- r 代替库中现有的文件或者插入新的文件,一般必选
- v 输出详细信息
注意:生成的库名必须遵守规范,lib[static_library_name].a:lib为前缀,中间是静态库名,扩展名为.a。 此外,静态库提供者同时应该提供头文件,使用该静态库的程序,必须包含头文件。
1.2.静态库的使用
. 应用程序需要连接外部库的情况下,linux默认对库的连接是使用动态库(.so),在找不到动态库的情况下再选择静态库(.a)。
因此,当链接目录下同时存在libtest.so 和libtest.a的时候,使用-ltest会链接libtest.so;
如果要想链接静态库有以下方法:
-
1、将静态库改名为libstatictest.a,使用-lstatictest选项。或者将动态库改名。
-
2、使用-static选择,但是:当对动态库与静态库混合连接的时候,使用-static会导致所有的库都使用静态连接的方式。
-
3、使用-Wl,-Wl,-Bstatic后面的-lxxxx系统会查找静态库;-Wl,-Bdynamic后面的-lxxxx,系统会查找动态库。
-
4、在CMakeLists.txt中可以将头文件路径和库文件路径分别赋值给
XXX_INCLUDE_DIRS XXX_LIBRARIES
SET(XXX_LIBRARIES /usr/local/lib/)
SET(XXX_INCLUDE_DIRS /usr/local/include/)
然后链接库:include_directories(
include
${XXX_INCLUDE_DIRS}
…
)target_link_libraries(${PROJECT_NAME}
${XXX_LIBRARIES}
…
)
动态库使用时必须指定绝对链接路径,或者将静态库拷贝到默认的动态库搜索路径下,或者将动态库所在目录加到$LD_LIBRARY_PATH内。或者在/etc/ld.so.conf 中添加指定的链接库搜索路径(需要root权限),注:需要运行 /sbin/ldconfig,以达到刷新 /etc/ld.so.cache的效果。
静态库可以使用相对路径,或者直接在编译选择后面加上文件位置而不使用-l例:g++ -o main main.cpp …/libhelloworld.a
1.3.静态库搜索顺序
- ld会去找GCC命令中的参数-L
- 再找gcc的环境变量LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
2.动态库
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。
动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
动态库特点:
- 动态库把对一些库函数的链接载入推迟到程序运行的时期。
- 可以实现进程之间的资源共享。(因此动态库也称为共享库)
- 将一些程序升级变得简单。
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显式调用)。
2.1.动态库生成
假设有三个源文件helloworld_1.c helloworld_2.c helloworld.h 以下代码可以生成libhelloworld.so
gcc -fPIC -c helloworld_1.c helloworld_2.c #生成helloworld_1.o helloworld_2.o
gcc -shared -o libhelloworld.so helloworld_1.o helloworld_2.o
或者:
gcc -fPIC -shared -o libhelloworld.so helloworld_1.c helloworld_2.c
2.2动态库使用
#include <dlfcn.h>,提供了下面几个接口:
- void * dlopen( const char * pathname, int mode ):函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。
- void* dlsym(void* handle,const char* symbol):dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。
- int dlclose (void *handle):dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
- const char *dlerror(void):当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
注意:显式调用C++动态库:
因为c++和c的符合命名的规则差异,显式调用C++动态库的时候,动态库中的接口最好都用extern "C"声明。注意:只有非成员函数才能被声明为extern “C”,并且不能被重载。冠以extern “C”限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。
在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这很难用显式调用做到。 最后,”显式”使用C++动态库中的Class是非常繁琐和危险的事情,因此能用”隐式”就不要用”显式”,能静态就不要用动态。
2.3.动态库搜索顺序
- 编译目标代码时指定的动态库搜索路径
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径
- 默认的动态库搜索路径/lib
- 默认的动态库搜索路径/usr/lib
3.头文件
除默认的/usr/include, /usr/local/include等include路径外, 还可以通过设置环境变量来添加系统include的路径:
#C
export C_INCLUDE_PATH=XXXX:$C_INCLUDE_PATH
#CPP
export CPLUS_INCLUDE_PATH=XXX:$CPLUS_INCLUDE_PATH
还可以编译时通过 -I选项来执行头文件目录。
注意:gcc不会搜索c++的头文件,所以编译c++文件的时候,记得用g++
4. XXX.a转XXX.so
#gcc -shared -o lib***.so lib***.a lib***.a lib***.a
gcc -shared -o liblapack.so libf2c.a libblas.a liblapack.a