库通俗的讲就是把一些常用的函数的目标文件打包在一起,提供相应的接口,便于程序员使用。库是写好的、现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。像我们中常用的输出printf函数。当我们在使用时并不关心其底层是如何实现,只是使用其来提高编写代码的效率。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
函数库分为静态库和动态库两种。
静态库: 在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库: 在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
静态库的特点:
1、静态库对函数库的链接是放在编译时期完成的。
2、程序在运行时与函数库没有关系。
3、浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库的特点:
1、动态库把对一些库函数的链接载入推迟到程序运行的时期。
2、可以实现进程之间的资源共享。(因此动态库也称为共享库)
3、 将一些程序升级变得简单。
4、甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
下面将通过举例说明在Linux中如何创建静态库和动态库,及其使用。
在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o 文件
先创建一个作业目录,保存本次练习的文件。
mkdir test1
cd test1
mkdir [-p] dirName
用于创建目录
cd [dirName]
命令用于切换当前工作目录
然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。
这里笔者以上一篇文章讲述过的vim编辑器为例
具体代码如下:
vim hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
vim hello.c
#include
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
vim main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 gcc 先编译成.o 文件。在系统提示符下键入以下命令得到 hello.o 文件
gcc -c hello.c
我们运行 ls 命令看看是否生存了 hello.o 文件。
ls
应得到结果:hello.c hello.h hello.o main.c
实际运行结果如图所示
例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a
ar -crv libmyhello.a hello.o
我们同样运行 ls
命令查看结果
ls命令结果中应有有 libmyhello.a
如何使用静态库的内部函数呢?
需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后再用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。
在main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。
方法一
gcc -o hello main.c -L. –lmyhello
自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提示 myhello 没定义
但是是系统的库时,如g++ -o main(-L/usr/lib) -lpthread main.cpp
就不出错。
方法二
gcc main.c libmyhello.a -o hello
方法三
首先生成 main.o:
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a
动态库连接时也可以这样做
我们还可以试试删除静态库文件,来确定公用函数hello是否真的连接到目标文件hello中
rm libmyhello.a
./hello
运行结果应为:Hello everyone!
这里笔者以方法二为例,经测试程序照常运行,表明静态库中的公用函数已经连接到目标文件中了。
实际运行结果如图所示
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。
例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyhello.so。用 gcc 来创建动态库。在系统提示符下键入以下命令得到动态库文件 libmyhello.so。
输入命令gcc -shared -fPIC -o libmyhello.so hello.o
(-o 不可少)
输入ls
命令看动态库文件是否生成。
应得到结果为:hello.c hello.h hello.o libmyhello.so main.c
实际运行结果如图所示:
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。
我们先运行 gcc 命令生成目标文件,再运行它看看结果。
gcc -o hello main.c -L. -lmyhello
(或 gcc main.c libmyhello.so -o hello
不会出错(没有 libmyhello.so 的话,会出错))
但是接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK了)
复制命令mv libmyhello.so /usr/lib
输入命令./hello
得到结果应为:Hello everyone!
这也进一步说明了动态库在程序运行时是需要的。
实际运行结果如图所示:
sudo passwd root
然后设置密码即可,而后输入su root
即可进入该模式。我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样,
那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?
接下来跟随笔者一起试试看吧。
(1)首先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
输入删除命令rm -f hello hello.o /usr/lib/libmyhello.s
ls
应得到结果为:hello.c hello.h main.c
(2)然后我们来创建静态库文件libmyhello.a 和动态库文件 libmyhello.so
输入命令
gcc -c hello.c
ar -cr libmyhello.a hello.o
(或-cvr )
gcc -shared -fPIC -o libmyhello.so hello.o
ls
应得到结果为:hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.so 都已经生成,并都在当前目录中。
(3)最后,我们运行 gcc 命令来使用函数库 myhello 生成目标文件 hello,并运行程序 hello。
输入命令gcc -o hello main.c -L. –lmyhello
(动态库和静态库同时存在时,优先使用动态库,当然,如果直接输入gcc main.c libmyhello.a -o hello
的话,就是指定为静态库了)
运行./hello
会得到/hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
由此我们从程序 hello 运行的结果中可以看出,当静态库和动态库同名时,gcc 命令将优先使用动
态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib中即可。
最主要的是 GCC 命令行的一个选项: -shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC
表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L.
表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:
gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gcc main.c libmyhello.a -o hello”)
-lmyhello
编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。
LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有 root 限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I”include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
从上述可知,如何找到生成的动态库有 3 种方式:
(1)把库拷贝到/usr/lib 和/lib 目录下。
(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。
例如动态库 libhello.so 在/home/example/lib 目录下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样,
加入的目录下的所有库文件都可见。
附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必须这样g++ -o main main.cpp -L/usr/lib -lpthread
才正确 。
自定义的库考到/usr/lib 下时,
g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a
会出错。
但是这样 g++-o main main.cpp -L/usr/lib -lpthread -lthread -lclass
就正确了。
1)编写一个主程序文件main.c 和两个子程序文件 sub1.c,sub2.c,要求:子程序sub1.c 包含一个算术运算函数 float x2x(int a,int b),此函数功能为对两个输入整型参数做某个运算,将结果做浮点数返回;再扩展写一个x2y函数(功能自定);main函数代码将调用x2x和x2y ;返回结果printf出来。
2)将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;
将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
3)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
(1)先创建一个作业目录,保存本次练习的文件。
mkdir test2
cd test2
笔者采用vim文本编辑器编辑所需文件
具体代码如下:
sub1.c
#include
float x2x(int a, int b)
{
float z;
z = a * b;
return z;
}
sub2.c
#include
float x2y(int a, int b)
{
float z;
z = a + b;
return z;
}
sub.h
#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
main.c
#include
using namespace std;
int main()
{
int x, y;
x = 3; y = 6;
float k,h;
k = x2x(x, y);
h =x2y(x,y);
printf("%f\n", k);
printf("%f\n", h);
return 0;
}
此处笔者将sub1.c函数设置为乘法运算,sub2.c函数为加法运算,main主函数调用并输出两个运算结果。
(2)将.c文件编译成.o文件
输入命令
gcc -c sub1.c
gcc -c sub2.c
(3)由.o 文件创建静态库。
将两个目标文件用ar工具生成.a静态库
ar -crv libsub.a sub1.o sub2.o
ls
命令查看结果
实际结果如图所示:
(4) 第 4 步:在程序中使用静态库。
此处采用方法一
输入命令使用静态库gcc -o sub main.c -L. -lsub
调用程序./sub
实际运行结果如图所示
至此,main函数的目标文件与静态库的链接,以及最终可执行程序生成完毕。
键入ll
用来查看静态库生成文件大小
(5)第 5 步:由.o 文件创建动态库文件。
同样我们创建动态库文件libsub.so,并使用该动态库,输入命令
gcc -shared -fPIC -o libsub.so sub1.o sub2.o
(6)第 6 步:在程序中使用动态库。
gcc -o sub main.c -L. -lsub
然后跟前面所述一样,使用root进行操作
mv libsub.so /usr/lib
./sub
至此,main函数的目标文件与动态库的链接,以及最终可执行程序生成完毕。键入ll
用来查看动态库生成文件大小。
本文主要讲述了如何使用gcc生成静态库和动态库,可使读者对此有了一个大体的了解。且本文通过实例演示,以及对上一篇文章要求进行了拓展并再次演示静态库与动态库的使用,笔者也通过此次仿写受益颇多,对此有了更深一步的理解。