一、库文件引入
当我们编写程序时用到了链表时,我们写了一个链表的声明list.h 及其实现list.c我们再将其编译链接到main函数中时就可以用了,但是当下次,做另一件事或者项目组里的其他成员要用这个链表时,他们也要重新编译这个链表的实现,这就导致每次使用每次都要编译,做了很多重复的工作。所以引入库后,将已经编译好的一个或多个文件放在一起,其他人可多次直接使用不用编译,他们并不清楚具体的实现,可以隐藏代码的实现,极大地提高了代码生成的速度,提高了团队效率。
库文件是一组预先编译好的方法或函数的集合,它们通常是一组相互关联的函数组成以执行某项常见的任务。标准系统库文件一般存储在/lib和/usr/lib目录中,C语言编译器(链接程序时)需要知道要搜索那些库文件,因为在默认情况下,它只搜索默认标准C语言库。
库文件的名字以lib开头随后的部分一般指明这是什么库(例如,c代表C语言库,m代表数学库),或者是自己建的库指定的库名,文件名的最后部分以.开始,然后给出库文件的类型:静态库 or共享库:
.a:代表传统的静态函数库
.so:代表共享函数库
二、静态库
静态库,也称作归档文件,他们文件名都是以.a为结尾的,比如,标准C语言函数库/usr/lib/libc.a和X11函数库/usr/lib/libX11.a
静态库是链接阶段直接将库中的内容合并到最终的可执行文件中,所以静态库就是一个中间文件的集合。
1.静态库的创建
该可执行文件包含add.c,max.c,main.c中的内容,将上述除main函数之外所有.c文件拷贝到同级目录下的一个文件夹liba里,再重新编译.c文件为.o文件,也可直接将上述编译好的.o文件拷贝过来,然后将.o文件打包成静态库,通过如下命令:
就创建好了一个静态库:libmyfoo.a
2.静态库的使用
新建一个test.c含有主函数和调用add()和max()的调用点的文件,并包含了“my.h”头文件,引入静态库编译时会出错:
gcc –o test test.c –lmyfoo
会出现一个错误:undifinie reference to ‘add.c’
库文件没引用,链接时错误:原因1.名字写错 2.方法确实现了在某个库文件里,但链接时没找那个库,默认只搜索标准的C库
此时,当然我们这里的错误是第二点,应指定库路径
gcc –o test test.c –L. –lmyfoo (-L指定路径,.指的是当前目录)
还有另一种库的使用方法:
gcc –o test test.c 路径/libxxx.a
若存在共享库与静态库名相同,例如都为myfoo时,用-lmyfoo引用库默认使用的是共享库。这时可以正确执行,若删掉该静态库myfoo.a,./test也可以正确执行???原因见下方四中二者别。
另外可以使用ldd命令,查看可执行文件执行过程中用到的库
ldd test
三、共享库
共享库也称动态库,是链接阶段只在可执行文件中设置使用的库(让可执行文件知道所用的功能代码在哪个库中),运行时,由操作系统将动态库单独动态加载到内核上执行。动态库叫做“不可执行的可执行文件”,这句可能给人感觉有歧义,理解一下:动态库是操作系统在用它的时候才加载到内存上执行的,所以就是说它本身是可以执行的,但是这个库里边一般没有main函数,它不可以单独执行,必须由main函数引用,所以“不可执行”指的是不可单独执行。
1.共享库的创建
1)将所有的.c文件编译成.o文件,
2)打包做成库:(打包除了main()所在的源文件,否则main函数会重复)
gcc –shared –fPIC –o libmyfoo.so *.o
第一步对于共享库来说不是必须的,也可以直接用.c文件生成共享库:
gcc –shared –fPIC –o libmyfoo.so *.c
2.共享库的使用
gcc –o main main.c –L路径 –l库名
-shared指定做的库是共享库
-fPIC 与代码位置无关
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码,则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
此时,使用gcc –o test test.c –L. –lmyfoo可编译成功,但是./test运行时失败,报错:“can not open shared object file”。
因为编译时指定库路径只是告诉gcc库的位置了,但运行时,操作系统加载动态库路径只有默认的两个:/lib和/usr/lib,所以./test找不到动态库,此时有两种解决方法,
1:root用户下,将libmyfoo.so剪切到/usr/lib标准库路径底下
2:设置环境变量LD_LIBRARY_PATH,使操作系统加载库时,除了在默认路径下搜索还需要到用户指定的路径下搜索
export LD_LIBRARY_PATH=绝对路径
当加载的库多并且它们不在一块的时候,绝对路径不可能写多个,这时,在自己有权限操作的地方,创建一个lib子目录,例如:/home/stu/lib生成的所有动态库都放到该目录下 ,然后,执行命令:
export LD_LIBRARY_PATH=/home/stu/lib
后执行 echo $LD_LIBRARY_PATH命令:
这时,重新打开以一个终端:
说明这样的设置仅仅对当前终端有效。而要使环境变量永久有效,修改配置文件(/home/user(根据自己的用户设置)/.bashrc),此处以在家目录底下的.bashrc为例:
执行命令: vim .bashrc
在最后加一行:export LD_LIBRARY_PATH=/home/stu/lib就好,但这个仅当前用户永久有效的。
四、静态库与共享库区别
静态库在最终执行时不依赖于库文件,也就是说,如果用静态库生成的可执行文件,当删去库时,仍可正常执行,因为库里的内容已经拷贝了一份到可执行文件里了,库存不存在已经没有关系了。
但用动态库(共享库)生成的可执行文件,执行时不仅需要可执行文件本身,还需要该动态库文件,如下图:
当你同时运行许多应用程序并且他们都使用来自同一个函数库的函数时,内存中就会有同一函数的多份副本,而且在程序文件自身中也有多分同样的副本。这将消耗大量宝贵的内存和磁盘空间,这就是静态库这一特点导致的。而此时动态库就弥补了这一不足。这里引用《Linux程序设计》这本书中的一段话:
共享库在许多方面类似于windows中使用的动态链接库。.so库对应于.DLL文件,他们都是在程序运行时加载,而.a库类似于.LIB文件,它们都包含在可执行程序中。即:
静态库 动态库
windows .lib .dll
Linux .a .so