10.4.1 动态库的基本概念
动态库又称为共享库。这种类型的库的命名规则一般是libxxx.M.N.so ,其中,xxx 为库的名字,M 是库的主版本号,N 是库的副版本号。当然也可以不要版本号,但名字必须有,即libxxx.so 。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,我们的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进程序,而是程序运行时动态地申请并调用,因此程序的运行环境中必须提供相应的库。动态函数库的改变并不影响程序,所以动态函数库的升级比较方便。Linux 系统有几个重要的目录用于存放相应的函数库,如/lib/usr/lib 。
当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们复制到执行文件,由于这种复制是完整的,因此一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样的。动态库会在执行程序内留下一个标记,指明当程序执行时,首先必须载入这个库。由于动态库节省空间,因此Linux 下进行连接的默认操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库连接。
10.4.2 动态库的创建和使用
动态库文件的后缀为.so ,可以直接使用gcc 或g++ 生成。下面来看一个例子。
【例10.3 】创建和使用动态库
(1 ) 打开Visual Studio Code ,新建一个源文件test.cpp ,内容如下:
#include
#include
using namespace std;
void f(int age)
{
cout << "your age is " << age << endl;
printf("age:%d\n",age);
}
代码很简单。这个源文件主要作为动态库。把源test.c 文件上传到Linux ,在命令行输入:
# g++ test.c pp -fPIC -shared -o libtest.so
此时会在同目录下生成动态库文件libtest.so 。上面命令行中的-shared 表明生成共享库,而-fPIC 则表明使用地址无关代码。PIC 的全称是Position Independent Code 。在Linux 下编译共享库时,必须加上-fPIC 参数,否则在链接时会有错误提示。那么fPIC 的目的是什么?共享库文件可能会被不同的进程加载到不同的位置上,如果共享对象中的指令使用了绝对地址、外部模块地址,那么在共享对象被加载时就必须根据相关模块的加载位置对这个地址做调整,也就是修改这些地址,让它在对应进程中能正确访问,这样就不能实现多进程共享一份物理内存,共享库在每个进程中都必须有一份物理内存的复制。fPIC 指令就是为了让使用同一个共享对象的多个进程能尽可能多地共享物理内存,它背后把那些涉及绝对地址、外部模块地址访问的地方都抽离出来,保证代码段的内容可以多进程相同,实现共享。这些内容了解即可。总之,-fPIC (或-fpic )表示编译为位置独立的代码。位置独立的代码即位置无关代码,在可执行程序加载的时候,可以存放在内存中的任何位置。若不使用该选项,则编译后的代码是位置相关的代码,在可执行程序加载时,通过代码复制的方式来满足不同进程的需要,没有实现真正意义上的位置共享。
(2 ) 动态库产生后,我们就可以使用动态库了,下面先编写一个主函数。打开Visual Studio Code ,新建一个文件main.cpp ,并输入如下代码:
extern void f(int age); // 声明要使用的函数
#include
using namespace std;
int main(int argc, char *argv[])
{
f(66);
cout << "HI" << endl;
return 0;
}
代码很简单。首先声明一下f ,然后就可以在main 函数中使用了。保存代码后将其上传到Linux ,注意要和libtest.a 放在同一个目录,然后在命令行进行编译并运行:
# g++ main.c -o main -L ./ -ltest
其中,-L 用来告诉g++ 去哪里找库文件,它后面加了空格和./ 表示在当前目录下寻找库,或者直接写-L. 即可;-l 用来指定具体的库,其中的lib 和.so 不用显式写出,g++ 会自动去寻找libtest.so 。默认情况下,g++ 或gcc 首先搜索动态库(.so )文件,找不到后再去寻找静态库(.a )文件。当前目录下以test 命名的库文件有动态库文件(libtest.so) ,因此g++ 可以找到。
编译链接后,会在当前目录下生成可执行文件main ,如果此时运行,会发现运行不了:
# ./main
./main: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
这是为什么呢?看提示似乎是main 程序找不到libtest.so ,但是main 文件和libtest.so 都在同一目录下。虽然我们知道它们在同一目录下,但程序main 并不知道。那怎么办呢?把动态库放到默认的搜索路径上,或者告诉系统动态库的路径即可,有以下3 种方法。
第一种,将库复制到/usr/lib 和/lib (不包含子目录)下。
这两个路径是默认搜索的地方,但要注意的是,把动态库放到这两个目录之一后,要执行命令ldconfig ,否则还是会提示找不到。现在我们把libtest.so 剪切到/usr/lib :
# mv libtest.so /usr/local/lib
移动后,当前目录下就没有libtest.so 了,然后执行ldconfig 后再运行main :
# ldconfig
# ./main
age:66
HI
很多开源软件通过源码包进行安装时,如果不指定--prefix ,就会将库安装在/usr/local/lib 目录下,当运行程序需要链接动态库时,提示找不到相关的.so 库,进而报错。也就是说,/usr/local/lib 目录不在系统默认的库搜索目录中。
第二种,在命令前加环境变量。
如果第一种方法做了,则首先把/usr/lib 或/lib 下的libtest.so 删除:
[root@localhost lib]# cd /usr/lib
[root@localhost lib]# rm -f libtest.so
再回到/zww/test 下重新生成一个libtest.so ,然后加环境变量后运行main :
# g++ test.cpp -fPIC -shared -o libtest.so
# LD_LIBRARY_PATH=/zww/test ./main
age:66
HI
可以看到,我们把动态库libtest.so 的路径/zww/test 赋值给了环境变量LD_LIBRARY_PATH ,然后运行main 就成功了。这种方法虽然简单,但该环境变量只对当前命令有效,当该命令执行完成后,该环境变量就无效了,除非每次执行main 都这样加环境变量。要想采用永=久法,可以参考第三种方法。
第三种,修改/etc/ld.so.conf 文件。
我们可以把自己的动态库文件的路径加到/etc/ld.so.conf 中,这个文件叫动态库配置文件,接着执行ldconfig ,然后系统可以把我们添加的路径作为其默认的搜索路径,一劳永逸。
# vi /etc/ld.so.conf
然后在该文件末尾新起一行加入我们的库路径/zww/test/ ,保存并关闭。此时查看/etc/ld.so.conf 的内容为:
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/zww/test/
其中,第一行原来就有。
现在开始执行main :
# ./main
age:66
HI
可以发现执行成功了。我们也可以把libtest.so 放到任意目录,然后添加任意目录的路径到/etc/ld.so.conf ,发现再也不用担心main 找不到ibtest.so 了。比如现在把libtest.so 放到/root/ 下,并执行main :
# mv libtest.so /root
# ./main
./main: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
预料之中,main 找不到libtest.so ,因为/zww/test 下没有了。我们来修改/etc/ld.so.conf ,把/root 添加进去,添加后的内容如下:
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/root
再执行ldconfig ,然后执行main :
# ldconfig
# ./main
age:66
HI
执行成功了。值得注意的是,每次修改/etc/ld.so.conf 后,都要执行ldconfig 。ldconfig 命令的用途主要是在默认搜寻目录(/lib 和/usr/lib )以及动态库配置文件/etc/ld.so.conf 所列的目录下,搜索出可共享的动态链接库(格式如前面介绍的lib*.so* ),进而创建出动态装入程序(ld.so 程序)所需的连接和缓存文件。缓存文件默认为/etc/ld.so.cache ,此文件保存已排好序的动态链接库的名字列表。
【例10.4 】多个文件生成动态库
(1 ) 打开Visual Studio Code ,新建一个源文件test1.cpp ,内容如下:
#include
using namespace std;
void f1(int age)
{
cout << "this is libtest1.so: " << age << endl;
}
(2 )保存后,再新建一个源文件test2.cpp ,内容如下:
#include
using namespace std;
void f2(int age)
{
cout << "this is libtest2.so: " << age << endl;
}
代码很简单。这两个源文件主要作为动态库。
(3 )把两个源文件上传到Linux ,在命令行下输入以下命令:
# g++ test1.cpp test2.cpp -fPIC -shared -o libtest.so
此时会在同目录下生成动态库文件libtest.so ,然后编译main :
# g++ main.cpp -L. -ltest -o main
此时把/zww/test 路径加入动态库配置文件/etc/ld.so.conf 中,加入后内容如下:
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/zww/test
执行ldconfig 后再执行main :
# ldconfig
# ./main
this is libtest1.so: 65
this is libtest2.so: 66
bye
运行成功了。其实多个文件组成库的过程和一个文件类似,只是编译库的时候多加一个源文件而已。
